package services

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"text/template"
	"time"

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

// DatosPlantilla es el contexto inyectado al renderizar una plantilla de correo
type DatosPlantilla struct {
	ClienteNombre    string
	ClienteEmpresa   string
	ClienteEmail     string
	Servicios        []ItemServicio
	Total            float64
	FechaVencimiento string
	DiasRestantes    int
	Asunto           string
	// EnlacePago es la URL de pago Bold para el ciclo actual.
	// Mismo link en todas las notificaciones hasta que el pago sea aprobado.
	EnlacePago string
}

type ItemServicio struct {
	Nombre    string
	Precio    float64
	Moneda    string
	FechaVenc string
}

// DatosEjemplo devuelve datos de prueba para la previsualización de plantillas
func DatosEjemplo() DatosPlantilla {
	return DatosPlantilla{
		ClienteNombre:  "Juan Pérez",
		ClienteEmpresa: "Empresa Demo S.A.",
		ClienteEmail:   "cliente@ejemplo.com",
		Servicios: []ItemServicio{
			{Nombre: "Hosting Basic", Precio: 29.99, Moneda: "USD", FechaVenc: "2025-12-31"},
			{Nombre: "Dominio .com", Precio: 14.99, Moneda: "USD", FechaVenc: "2025-12-31"},
		},
		Total:            44.98,
		FechaVencimiento: "31/12/2025",
		DiasRestantes:    15,
		Asunto:           "Recordatorio de vencimiento",
		EnlacePago:       "https://checkout.bold.co/payment/LNK_ejemplo123",
	}
}

// RenderPlantilla renderiza el CuerpoHTML de una PlantillaCorreo con los datos dados
func RenderPlantilla(p *models.PlantillaCorreo, datos DatosPlantilla) (string, error) {
	tmpl, err := template.New("correo").Parse(p.CuerpoHTML)
	if err != nil {
		return "", fmt.Errorf("plantilla HTML inválida: %w", err)
	}
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, datos); err != nil {
		return "", fmt.Errorf("error al renderizar plantilla: %w", err)
	}
	return buf.String(), nil
}

// EnviarCorreoPrueba envía la plantilla con datos de ejemplo al email dado
func EnviarCorreoPrueba(email string, p *models.PlantillaCorreo) error {
	html, err := RenderPlantilla(p, DatosEjemplo())
	if err != nil {
		return err
	}
	return app.Http.Mail.Send(email, "[PRUEBA] "+p.Asunto, html)
}

// EnviarCorreoManual envía correo de aviso para un contrato específico.
// Usa la primera regla activa para obtener la plantilla y la pasarela de pago;
// si no hay regla, usa la primera plantilla disponible con pasarela "bold".
func EnviarCorreoManual(contrato *models.Contrato) error {
	// Intentar obtener plantilla y pasarela desde la primera regla activa
	var p *models.PlantillaCorreo
	var reglaID uint
	gateway := "bold"

	reglas, err := models.GetReglasActivas()
	if err == nil && len(reglas) > 0 {
		regla := reglas[0]
		reglaID = regla.ID
		gateway = regla.PasarelaEnlace
		p, err = models.GetPlantillaByID(regla.PlantillaID)
		if err != nil {
			p = nil
		}
	}

	// Fallback: primera plantilla disponible
	if p == nil {
		plantillas, err2 := models.GetAllPlantillasSelect()
		if err2 != nil || len(plantillas) == 0 {
			return fmt.Errorf("no hay plantillas de correo disponibles")
		}
		p, err = models.GetPlantillaByID(plantillas[0].ID)
		if err != nil {
			return err
		}
	}

	// Generar o reutilizar el enlace de pago
	enlacePago := ObtenerOCrearEnlacePago(contrato, gateway)

	dias := int(time.Until(contrato.FechaVencimiento).Hours() / 24)
	var items []ItemServicio
	for _, s := range contrato.Servicios {
		items = append(items, ItemServicio{
			Nombre:    s.Nombre,
			Precio:    s.Precio,
			Moneda:    s.Moneda,
			FechaVenc: contrato.FechaVencimiento.Format("02/01/2006"),
		})
	}
	datos := DatosPlantilla{
		ClienteNombre:    contrato.Cliente.Nombre,
		ClienteEmpresa:   contrato.Cliente.Empresa,
		ClienteEmail:     contrato.Cliente.Email,
		FechaVencimiento: contrato.FechaVencimiento.Format("02/01/2006"),
		DiasRestantes:    dias,
		Total:            contrato.PrecioAcordado,
		Servicios:        items,
		EnlacePago:       enlacePago,
	}
	html, err := RenderPlantilla(p, datos)
	if err != nil {
		return err
	}

	// Crear log previo (pendiente)
	idsJSON, _ := json.Marshal([]uint{contrato.ID})
	logEntry := models.NotificacionLog{
		ClienteID:    contrato.ClienteID,
		ReglaID:      reglaID,
		ContratosIDs: string(idsJSON),
		FechaEnvio:   time.Now(),
		Estado:       "pendiente",
		Asunto:       p.Asunto,
		PreviewHTML:  html,
	}
	savedLog, _ := models.CreateNotificacionLog(logEntry)

	sendErr := app.Http.Mail.Send(contrato.Cliente.Email, p.Asunto, html)

	// Actualizar estado del log
	if savedLog != nil {
		if sendErr != nil {
			savedLog.Estado = "fallido"
			savedLog.ErrorMsg = sendErr.Error()
		} else {
			savedLog.Estado = "enviado"
		}
		models.UpdateNotificacionLog(*savedLog)
	}

	return sendErr
}

// EnviarNotificacionGrupo envía un correo agrupado para un cliente con múltiples contratos.
// pasarela indica la pasarela de pago que originó el evento (bold | dlocal | "").
func EnviarNotificacionGrupo(regla *models.NotificacionRegla, cliente *models.Cliente, contratos []models.Contrato, pasarela string) error {
	p, err := models.GetPlantillaByID(regla.PlantillaID)
	if err != nil {
		return fmt.Errorf("plantilla no encontrada: %w", err)
	}

	var items []ItemServicio
	var total float64
	var fechaVenc time.Time

	for _, c := range contratos {
		for _, s := range c.Servicios {
			items = append(items, ItemServicio{
				Nombre:    s.Nombre,
				Precio:    s.Precio,
				Moneda:    s.Moneda,
				FechaVenc: c.FechaVencimiento.Format("02/01/2006"),
			})
		}
		total += c.PrecioAcordado
		if fechaVenc.IsZero() || c.FechaVencimiento.Before(fechaVenc) {
			fechaVenc = c.FechaVencimiento
		}
	}

	dias := int(time.Until(fechaVenc).Hours() / 24)

	// Generar o reutilizar el enlace de pago para el primer contrato del grupo
	enlacePago := ""
	if len(contratos) > 0 {
		enlacePago = ObtenerOCrearEnlacePago(&contratos[0], regla.PasarelaEnlace)
	}

	datos := DatosPlantilla{
		ClienteNombre:    cliente.Nombre,
		ClienteEmpresa:   cliente.Empresa,
		ClienteEmail:     cliente.Email,
		Servicios:        items,
		Total:            total,
		FechaVencimiento: fechaVenc.Format("02/01/2006"),
		DiasRestantes:    dias,
		Asunto:           p.Asunto,
		EnlacePago:       enlacePago,
	}

	html, err := RenderPlantilla(p, datos)
	if err != nil {
		return err
	}

	// Construir JSON de IDs de contratos para el log
	var ids []uint
	for _, c := range contratos {
		ids = append(ids, c.ID)
	}
	idsJSON, _ := json.Marshal(ids)

	// Crear log previo (pendiente)
	logEntry := models.NotificacionLog{
		ClienteID:    cliente.ID,
		ReglaID:      regla.ID,
		ContratosIDs: string(idsJSON),
		FechaEnvio:   time.Now(),
		Estado:       "pendiente",
		Asunto:       p.Asunto,
		PreviewHTML:  html,
		Pasarela:     pasarela,
		EnlacePago:   enlacePago,
	}
	savedLog, err := models.CreateNotificacionLog(logEntry)
	if err != nil {
		log.Printf("Error guardando log de notificación: %v", err)
	}

	// Enviar correo (también CC si está definido)
	sendErr := app.Http.Mail.Send(cliente.Email, p.Asunto, html)
	if cliente.EmailCC != "" {
		_ = app.Http.Mail.Send(cliente.EmailCC, "[CC] "+p.Asunto, html)
	}

	// Actualizar estado del log
	if savedLog != nil {
		if sendErr != nil {
			savedLog.Estado = "fallido"
			savedLog.ErrorMsg = sendErr.Error()
		} else {
			savedLog.Estado = "enviado"
		}
		models.UpdateNotificacionLog(*savedLog)
	}

	return sendErr
}

// ReenviarLog reenvía un correo ya registrado en el historial
func ReenviarLog(logEntry *models.NotificacionLog) error {
	if logEntry.PreviewHTML == "" {
		return fmt.Errorf("no hay HTML guardado para este envío")
	}
	sendErr := app.Http.Mail.Send(logEntry.Cliente.Email, logEntry.Asunto, logEntry.PreviewHTML)
	if sendErr != nil {
		logEntry.Estado = "fallido"
		logEntry.ErrorMsg = sendErr.Error()
	} else {
		logEntry.Estado = "enviado"
		logEntry.ErrorMsg = ""
	}
	models.UpdateNotificacionLog(*logEntry)
	return sendErr
}

// EnviarCorreoConfirmacionPago envía el correo de "pago recibido" para un contrato.
// Usa la regla/plantilla con tipo_evento = 'pago_recibido'. Si no existe, no hace nada.
// Es idempotente: no reenvía si ya se envió hoy (YaEnviadoHoy por regla+cliente).
// EnviarCorreoConfirmacionPago envía el correo de "pago recibido" para un contrato.
// pasarela (opcional): "bold", "dlocal", etc. Se guarda en el historial del cliente.
func EnviarCorreoConfirmacionPago(contratoID uint, pasarela ...string) {
	pas := ""
	if len(pasarela) > 0 {
		pas = pasarela[0]
	}

	contrato, err := models.GetContratoByID(contratoID)
	if err != nil {
		log.Printf("[PAGO-CORREO] contrato %d no encontrado: %v", contratoID, err)
		return
	}

	reglas, err := models.GetReglasByTipoEvento("pago_recibido")
	if err != nil || len(reglas) == 0 {
		log.Printf("[PAGO-CORREO] sin regla pago_recibido configurada — se omite correo de confirmación")
		return
	}
	regla := &reglas[0]

	if err := EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}, pas); err != nil {
		log.Printf("[PAGO-CORREO] error enviando correo de confirmación para contrato %d: %v", contratoID, err)
	} else {
		log.Printf("[PAGO-CORREO] correo de confirmación enviado para contrato %d → %s (pasarela: %s)", contratoID, contrato.Cliente.Email, pas)
	}
}

// ObtenerOCrearEnlacePago devuelve el enlace de pago vigente del contrato.
// gateway puede ser "bold", "dlocal" o "ninguna"/"".
// Si ya existe un enlace guardado, lo reutiliza (mismo ciclo).
// Cuando el pago se aprueba, llamar models.LimpiarEnlacePago para el siguiente ciclo.
func ObtenerOCrearEnlacePago(contrato *models.Contrato, gateway string) string {
	// Normalizar gateway vacío → bold por defecto
	if gateway == "" {
		gateway = "bold"
	}
	if gateway == "ninguna" {
		return ""
	}

	// Reutilizar si ya hay uno vigente
	if contrato.EnlacePago != "" {
		return contrato.EnlacePago
	}

	switch gateway {
	case "dlocal":
		return obtenerEnlaceDlocal(contrato)
	default: // "bold"
		return obtenerEnlaceBold(contrato)
	}
}

// obtenerEnlaceBold crea un payment link en Bold y lo persiste en el contrato.
func obtenerEnlaceBold(contrato *models.Contrato) string {
	cfg, err := models.GetBoldConfig()
	if err != nil {
		log.Printf("[BOLD] sin config activa para contrato %d", contrato.ID)
		return ""
	}

	desc := fmt.Sprintf("Renovación contrato #%d", contrato.ID)
	if len(contrato.Servicios) > 0 {
		desc = fmt.Sprintf("Renovación: %s", contrato.Servicios[0].Nombre)
	}

	req := BoldPaymentLinkRequest{
		AmountType: "CLOSE",
		Amount: BoldAmountField{
			Currency:    "COP",
			TotalAmount: int64(contrato.PrecioAcordado),
		},
		Description: desc,
		Reference:   fmt.Sprintf("contrato-%d", contrato.ID),
		CallbackURL: cfg.CallbackUrl + fmt.Sprintf("?ref=contrato-%d", contrato.ID),
	}

	result, err := CreateBoldPaymentLink(cfg, req)
	if err != nil {
		log.Printf("[BOLD] error creando link para contrato %d: %v", contrato.ID, err)
		return ""
	}

	_ = models.GuardarEnlacePago(contrato.ID, result.Payload.PaymentLink, result.Payload.URL)
	contrato.EnlacePago = result.Payload.URL
	contrato.EnlacePagoLinkID = result.Payload.PaymentLink
	return result.Payload.URL
}

// obtenerEnlaceDlocal crea un pago en dLocal y devuelve la URL de redirección.
func obtenerEnlaceDlocal(contrato *models.Contrato) string {
	cfg, err := models.GetLastActiveDlocalApi()
	if err != nil {
		log.Printf("[DLOCAL] sin config activa para contrato %d", contrato.ID)
		return ""
	}

	desc := fmt.Sprintf("Renovación contrato #%d", contrato.ID)
	if len(contrato.Servicios) > 0 {
		desc = fmt.Sprintf("Renovación: %s", contrato.Servicios[0].Nombre)
	}

	pagoReq := PagoRequest{
		Currency:    "COP",
		Amount:      contrato.PrecioAcordado,
		OrderID:     int(contrato.ID),
		Description: desc,
		SuccessURL:  cfg.UrlProd + "/pago-exitoso",
		BackURL:     cfg.UrlProd + "/pago-cancelado",
	}

	body, err := CreatePago(*cfg, pagoReq)
	if err != nil {
		log.Printf("[DLOCAL] error creando pago para contrato %d: %v", contrato.ID, err)
		return ""
	}

	var resp struct {
		RedirectURL string `json:"redirect_url"`
		ID          string `json:"id"`
	}
	if err := json.Unmarshal(body, &resp); err != nil || resp.RedirectURL == "" {
		log.Printf("[DLOCAL] respuesta sin redirect_url para contrato %d: %s", contrato.ID, string(body))
		return ""
	}

	_ = models.GuardarEnlacePago(contrato.ID, resp.ID, resp.RedirectURL)
	contrato.EnlacePago = resp.RedirectURL
	contrato.EnlacePagoLinkID = resp.ID
	return resp.RedirectURL
}
