package services

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)

const boldAPIBase = "https://integrations.api.bold.co"

// ─── Estructuras de request/response ─────────────────────────────────────────

// BoldPaymentLinkRequest es el payload para crear un link de pago en Bold.
type BoldPaymentLinkRequest struct {
	AmountType  string          `json:"amount_type"`
	Amount      BoldAmountField `json:"amount"`
	Description string          `json:"description"`
	CallbackURL string          `json:"callback_url"`
	PayerEmail  string          `json:"payer_email,omitempty"`
	Reference   string          `json:"reference"`
}

// BoldAmountField representa el campo de monto (COP sin centavos).
type BoldAmountField struct {
	Currency    string `json:"currency"`
	TotalAmount int64  `json:"total_amount"`
}

// BoldPaymentLinkResponse es la respuesta de la API al crear el link.
type BoldPaymentLinkResponse struct {
	Payload struct {
		PaymentLink string `json:"payment_link"`
		URL         string `json:"url"`
	} `json:"payload"`
}

// BoldWebhookEvent es la estructura del JSON que Bold envía en cada notificación.
type BoldWebhookEvent struct {
	ID      string `json:"id"`
	Type    string `json:"type"`
	Subject string `json:"subject"`
	Data    struct {
		PaymentID string `json:"payment_id"`
		Amount    struct {
			Total    int64  `json:"total"`
			Currency string `json:"currency"`
		} `json:"amount"`
		PayerEmail string `json:"payer_email"`
		Metadata   struct {
			Reference string `json:"reference"`
		} `json:"metadata"`
	} `json:"data"`
}

// ─── Funciones del servicio ───────────────────────────────────────────────────

// boldAPIKey devuelve la API key correcta según el modo configurado.
func boldAPIKey(cfg *models.BoldConfig) string {
	if cfg.Modo == "production" {
		return cfg.ApiKeyProd
	}
	return cfg.ApiKeyTest
}

// boldSecretKey devuelve la secret key según el modo.
// En test la spec oficial indica cadena vacía.
func boldSecretKey(cfg *models.BoldConfig) string {
	if cfg.Modo == "production" {
		return cfg.SecretKeyProd
	}
	return cfg.SecretKeyTest
}

// CreateBoldPaymentLink crea un link de pago en Bold y devuelve la respuesta.
func CreateBoldPaymentLink(cfg *models.BoldConfig, req BoldPaymentLinkRequest) (*BoldPaymentLinkResponse, error) {
	if req.CallbackURL == "" {
		req.CallbackURL = cfg.CallbackUrl
	}

	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("bold: marshal request: %w", err)
	}

	httpReq, err := http.NewRequest("POST", boldAPIBase+"/online/link/v1", bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	httpReq.Header.Set("Authorization", "x-api-key "+boldAPIKey(cfg))
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("Accept", "application/json")

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("bold: http call: %w", err)
	}
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("bold API status %d: %s", resp.StatusCode, string(respBody))
	}

	var result BoldPaymentLinkResponse
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("bold: unmarshal response: %w", err)
	}
	return &result, nil
}

// GetBoldPaymentLinkStatus consulta el estado de un payment link existente.
func GetBoldPaymentLinkStatus(cfg *models.BoldConfig, linkID string) ([]byte, error) {
	url := fmt.Sprintf("%s/online/link/v1/%s", boldAPIBase, linkID)
	httpReq, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	httpReq.Header.Set("Authorization", "x-api-key "+boldAPIKey(cfg))
	httpReq.Header.Set("Accept", "application/json")

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// VerifyBoldSignature verifica la firma HMAC-SHA256 del webhook.
// Algoritmo: encoded = base64(rawBody); computed = HMAC-SHA256(encoded, secretKey) en hex.
// En modo test la secretKey es cadena vacía.
func VerifyBoldSignature(rawBody []byte, signature string, cfg *models.BoldConfig) bool {
	secret := boldSecretKey(cfg)
	encoded := base64.StdEncoding.EncodeToString(rawBody)
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(encoded))
	computed := hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(computed), []byte(signature))
}

// ParseBoldWebhookEvent parsea el JSON crudo del webhook en la estructura BoldWebhookEvent.
func ParseBoldWebhookEvent(rawBody []byte) (*BoldWebhookEvent, error) {
	var event BoldWebhookEvent
	if err := json.Unmarshal(rawBody, &event); err != nil {
		return nil, err
	}
	return &event, nil
}

// ─── Consulta directa de estado de link ──────────────────────────────────────

// BoldLinkStatus refleja la respuesta de GET /online/link/v1/{payment_link}.
// La API de Bold devuelve los campos directamente en el root del JSON (no dentro de "payload").
type BoldLinkStatus struct {
	ID            string `json:"id"`
	Status        string `json:"status"`         // ACTIVE | PROCESSING | PAID | REJECTED | CANCELLED | EXPIRED
	TransactionID string `json:"transaction_id"` // ID de la transacción cuando está pagado
	Reference     string `json:"reference"`
	Total         int64  `json:"total"`
}

// CheckBoldLinkStatus consulta la API de Bold y devuelve el status del link.
// Posibles valores: ACTIVE, PROCESSING, PAID, REJECTED, CANCELLED, EXPIRED.
func CheckBoldLinkStatus(cfg *models.BoldConfig, linkID string) (status string, transactionID string, monto int64, err error) {
	raw, err := GetBoldPaymentLinkStatus(cfg, linkID)
	if err != nil {
		return "", "", 0, err
	}
	var result BoldLinkStatus
	if err := json.Unmarshal(raw, &result); err != nil {
		return "", "", 0, fmt.Errorf("bold: parse link status: %w", err)
	}
	return result.Status, result.TransactionID, result.Total, nil
}

// CheckBoldLinkPaid es un wrapper de compatibilidad sobre CheckBoldLinkStatus.
// Devuelve (pagado, transactionID, monto, error).
func CheckBoldLinkPaid(cfg *models.BoldConfig, linkID string) (bool, string, int64, error) {
	st, txID, monto, err := CheckBoldLinkStatus(cfg, linkID)
	if err != nil {
		return false, "", 0, err
	}
	return st == "PAID" || st == "APPROVED", txID, monto, nil
}
