package services

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"net/http"
	"strings"
	"time"

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

var pagoExternoHTTPClient = &http.Client{Timeout: 15 * time.Second}

// ConfirmarPagoExternoPorReferencia resuelve una referencia de webhook con
// prefijo "extpay-" (las de Contrato usan "contrato-{id}") y, si corresponde,
// marca la solicitud como pagada y dispara la notificación al dueño del
// servicio. Devuelve manejada=true cuando la referencia era de este tipo, para
// que el webhook que llama sepa que no debe intentar resolverla como contrato.
func ConfirmarPagoExternoPorReferencia(referencia, pasarelaTxID, pasarela string) (manejada bool) {
	if !strings.HasPrefix(referencia, "extpay-") {
		return false
	}
	ok, solicitud, err := models.MarcarSolicitudPagoExternaPagada(referencia, pasarelaTxID)
	if err != nil {
		log.Printf("[PAGO_EXTERNO] error marcando %s como pagada: %v", referencia, err)
		return true
	}
	if !ok {
		log.Printf("[PAGO_EXTERNO] %s ya estaba confirmada, se ignora notificación duplicada", referencia)
		return true
	}
	log.Printf("[PAGO_EXTERNO] %s confirmada vía %s", referencia, pasarela)
	go EntregarNotificacionPagoExterno(solicitud)
	return true
}

// SolicitarPagoExterno crea un cobro para un ServicioPagoExterno ya
// autenticado (token+IP verificados por el middleware) usando una de sus
// pasarelas habilitadas.
func SolicitarPagoExterno(servicio *models.ServicioPagoExterno, referenciaExterna, pasarela, descripcion, moneda string, monto float64, clienteID *uint) (*models.SolicitudPagoExterna, error) {
	if monto <= 0 {
		return nil, fmt.Errorf("monto debe ser mayor a 0")
	}
	habilitadas := servicio.PasarelasList()
	if len(habilitadas) == 0 {
		return nil, fmt.Errorf("este servicio no tiene ninguna pasarela habilitada, contacta al administrador")
	}
	if pasarela == "" {
		if len(habilitadas) == 1 {
			pasarela = habilitadas[0]
		} else {
			return nil, fmt.Errorf("especifica 'pasarela': este servicio tiene varias habilitadas (%v)", habilitadas)
		}
	} else if !servicio.PasarelaHabilitada(pasarela) {
		return nil, fmt.Errorf("pasarela '%s' no está habilitada para este servicio (habilitadas: %v)", pasarela, habilitadas)
	}
	if clienteID != nil {
		if _, err := models.GetClienteByID(*clienteID); err != nil {
			return nil, fmt.Errorf("cliente_id inválido: %w", err)
		}
	}

	referenciaInterna, err := models.GenerarReferenciaInterna()
	if err != nil {
		return nil, err
	}
	if descripcion == "" {
		descripcion = fmt.Sprintf("Cobro %s", referenciaInterna)
	}
	if moneda == "" {
		moneda = "COP"
	}

	var enlace, linkID string
	switch pasarela {
	case "bold":
		enlace, linkID, err = crearCobroBoldExterno(referenciaInterna, descripcion, moneda, monto)
	case "dlocal":
		enlace, linkID, err = crearCobroDlocalExterno(referenciaInterna, descripcion, moneda, monto)
	case "paypal":
		enlace, linkID, err = crearCobroPaypalExterno(referenciaInterna, descripcion, moneda, monto)
	default:
		err = fmt.Errorf("pasarela '%s' no soportada", pasarela)
	}
	if err != nil {
		return nil, err
	}

	solicitud := &models.SolicitudPagoExterna{
		ServicioID:        servicio.ID,
		ReferenciaExterna: referenciaExterna,
		ReferenciaInterna: referenciaInterna,
		ClienteID:         clienteID,
		Pasarela:          pasarela,
		Monto:             monto,
		Moneda:            moneda,
		Descripcion:       descripcion,
		Estado:            "pendiente",
		EnlacePago:        enlace,
		PasarelaLinkID:    linkID,
	}
	if err := models.CreateSolicitudPagoExterna(solicitud); err != nil {
		return nil, fmt.Errorf("no se pudo guardar la solicitud: %w", err)
	}
	return solicitud, nil
}

func crearCobroBoldExterno(referencia, descripcion, moneda string, monto float64) (enlace, linkID string, err error) {
	cfg, err := models.GetBoldConfig()
	if err != nil {
		return "", "", fmt.Errorf("Bold no está configurado")
	}
	req := BoldPaymentLinkRequest{
		AmountType: "CLOSE",
		Amount: BoldAmountField{
			Currency:    moneda,
			TotalAmount: int64(math.Round(monto)),
		},
		Description: descripcion,
		Reference:   referencia,
		CallbackURL: cfg.CallbackUrl + "?ref=" + referencia,
	}
	result, err := CreateBoldPaymentLink(cfg, req)
	if err != nil {
		return "", "", err
	}
	return result.Payload.URL, result.Payload.PaymentLink, nil
}

func crearCobroDlocalExterno(referencia, descripcion, moneda string, monto float64) (enlace, linkID string, err error) {
	cfg, err := models.GetLastActiveDlocalApi()
	if err != nil {
		return "", "", fmt.Errorf("dLocal no está configurado")
	}
	pagoReq := PagoRequest{
		Currency:        moneda,
		Amount:          monto,
		OrderID:         referencia,
		Description:     descripcion,
		SuccessURL:      absAppURL("/pago-exitoso?ref=" + referencia),
		BackURL:         absAppURL("/pago-cancelado?ref=" + referencia),
		NotificationURL: absAppURL("/webhooks/dlocal"),
	}
	body, err := CreatePago(*cfg, pagoReq)
	if err != nil {
		return "", "", err
	}
	var resp struct {
		RedirectURL string `json:"redirect_url"`
		ID          string `json:"id"`
	}
	if err := json.Unmarshal(body, &resp); err != nil || resp.RedirectURL == "" {
		return "", "", fmt.Errorf("dLocal no devolvió redirect_url: %s", string(body))
	}
	return resp.RedirectURL, resp.ID, nil
}

func crearCobroPaypalExterno(referencia, descripcion, moneda string, monto float64) (enlace, linkID string, err error) {
	cfg, err := models.GetPaypalConfig()
	if err != nil {
		return "", "", fmt.Errorf("PayPal no está configurado")
	}
	orderID, urlAprobacion, err := PaypalCrearOrden(cfg, referencia, descripcion, moneda, monto)
	if err != nil {
		return "", "", err
	}
	return urlAprobacion, orderID, nil
}

// EntregarNotificacionPagoExterno avisa al dueño del ServicioPagoExterno que
// un cobro se confirmó, por los canales que tenga habilitados (webhook
// firmado y/o Telegram). Llamar solo una vez, justo después de que
// MarcarSolicitudPagoExternaPagada confirme la transición real (ok=true) —
// si no hay transición real (ya estaba pagada), no se vuelve a notificar.
func EntregarNotificacionPagoExterno(solicitud *models.SolicitudPagoExterna) {
	if solicitud == nil {
		return
	}
	if solicitud.Servicio == nil {
		full, err := models.GetSolicitudPagoExternaByReferenciaInterna(solicitud.ReferenciaInterna)
		if err != nil {
			log.Printf("[PAGO_EXTERNO] no se pudo recargar la solicitud %s para notificar: %v", solicitud.ReferenciaInterna, err)
			return
		}
		solicitud = full
	}
	servicio := solicitud.Servicio
	if servicio == nil {
		log.Printf("[PAGO_EXTERNO] solicitud %s sin servicio asociado, no se puede notificar", solicitud.ReferenciaInterna)
		return
	}

	payload := map[string]interface{}{
		"referencia_externa": solicitud.ReferenciaExterna,
		"referencia_interna": solicitud.ReferenciaInterna,
		"estado":             solicitud.Estado,
		"pasarela":           solicitud.Pasarela,
		"monto":              solicitud.Monto,
		"moneda":             solicitud.Moneda,
		"pasarela_tx_id":     solicitud.PasarelaTxID,
		"fecha_pago":         solicitud.FechaPago,
	}
	body, _ := json.Marshal(payload)

	webhookOk := true
	if servicio.NotificarWebhook && servicio.CallbackURL != "" {
		webhookOk = enviarCallbackFirmado(servicio.CallbackURL, servicio.CallbackSecret, body)
		if !webhookOk {
			log.Printf("[PAGO_EXTERNO] fallo entregando callback de %s a %s", solicitud.ReferenciaInterna, servicio.CallbackURL)
		}
	}

	telegramOk := true
	if servicio.NotificarTelegram && servicio.TelegramChatID != "" {
		telegramOk = enviarTelegramPagoExterno(servicio.TelegramChatID, solicitud)
	}

	if err := models.MarcarCallbackEntregado(solicitud.ID, webhookOk, telegramOk); err != nil {
		log.Printf("[PAGO_EXTERNO] error registrando intento de entrega para %s: %v", solicitud.ReferenciaInterna, err)
	}
}

func enviarCallbackFirmado(url, secret string, body []byte) bool {
	req, err := http.NewRequest("POST", url, bytes.NewReader(body))
	if err != nil {
		return false
	}
	req.Header.Set("Content-Type", "application/json")
	if secret != "" {
		mac := hmac.New(sha256.New, []byte(secret))
		mac.Write(body)
		req.Header.Set("X-Signature", hex.EncodeToString(mac.Sum(nil)))
	}
	resp, err := pagoExternoHTTPClient.Do(req)
	if err != nil {
		return false
	}
	defer resp.Body.Close()
	return resp.StatusCode >= 200 && resp.StatusCode < 300
}

func enviarTelegramPagoExterno(chatID string, s *models.SolicitudPagoExterna) bool {
	_, tgCfg, err := models.GetAgenteBotConfig()
	if err != nil || tgCfg == nil || tgCfg.BotToken == "" {
		log.Printf("[PAGO_EXTERNO] no hay bot de Telegram del agente configurado para notificar %s", s.ReferenciaInterna)
		return false
	}
	msg := fmt.Sprintf("💰 Pago confirmado\nRef: %s\nMonto: %.2f %s\nPasarela: %s",
		s.ReferenciaExterna, s.Monto, s.Moneda, s.Pasarela)
	svc := &TelegramService{BotToken: tgCfg.BotToken}
	if err := svc.SendMessage(chatID, msg); err != nil {
		log.Printf("[PAGO_EXTERNO] error enviando Telegram para %s: %v", s.ReferenciaInterna, err)
		return false
	}
	return true
}
