package controllers

import (
	"math"
	"strconv"
	"time"

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

func ContratosView(c *fiber.Ctx) error {
	return c.Render("renovaciones/contratos", fiber.Map{
		"user":    c.Locals("user"),
		"modules": c.Locals("userModules"),
	}, "layouts/main")
}

func GetContratos(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "10"))
	if page < 1 {
		page = 1
	}
	offset := (page - 1) * limit
	records, total, err := models.GetAllContratos(limit, offset, c.Query("search"), c.Query("estado"))
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Enriquecer con días restantes
	type ContratoDTO struct {
		models.Contrato
		DiasRestantes  int     `json:"dias_restantes"`
		Urgencia       string  `json:"urgencia"` // verde | amarillo | rojo
		PrecioSugerido float64 `json:"precio_sugerido"`
	}
	dtos := make([]ContratoDTO, len(records))
	now := time.Now()
	for i, r := range records {
		dias := int(r.FechaVencimiento.Sub(now).Hours() / 24)
		urgencia := "verde"
		if dias <= 0 {
			urgencia = "rojo"
		} else if dias <= 30 {
			urgencia = "amarillo"
		}
		var sugerido float64
		for _, s := range r.Servicios {
			sugerido += s.Precio
		}
		dtos[i] = ContratoDTO{Contrato: r, DiasRestantes: dias, Urgencia: urgencia, PrecioSugerido: sugerido}
	}
	return c.JSON(fiber.Map{
		"registros":  dtos,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
		"limit":      limit,
	})
}

func CreateContrato(c *fiber.Ctx) error {
	type Input struct {
		ClienteID        uint    `json:"cliente_id"`
		ServicioIDs      []uint  `json:"servicio_ids"`
		FechaInicio      string  `json:"fecha_inicio"`
		FechaVencimiento string  `json:"fecha_vencimiento"`
		PrecioAcordado   float64 `json:"precio_acordado"`
		AutoRenovar      bool    `json:"auto_renovar"`
		Notas            string  `json:"notas"`
		ServidorIDs      []uint  `json:"servidor_ids"`
		ConxDbIDs        []uint  `json:"conx_db_ids"`
		AiConfigIDs      []uint  `json:"ai_config_ids"`
	}
	var inp Input
	if err := c.BodyParser(&inp); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	fi, err := time.Parse("2006-01-02", inp.FechaInicio)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Fecha inicio inválida"})
	}
	fv, err := time.Parse("2006-01-02", inp.FechaVencimiento)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Fecha vencimiento inválida"})
	}
	m := models.Contrato{
		ClienteID:        inp.ClienteID,
		FechaInicio:      fi,
		FechaVencimiento: fv,
		PrecioAcordado:   float64(inp.PrecioAcordado),
		AutoRenovar:      inp.AutoRenovar,
		Notas:            inp.Notas,
		Estado:           "activo",
	}
	if inp.ServidorIDs == nil {
		inp.ServidorIDs = []uint{}
	}
	if inp.ConxDbIDs == nil {
		inp.ConxDbIDs = []uint{}
	}
	if inp.AiConfigIDs == nil {
		inp.AiConfigIDs = []uint{}
	}
	if err := models.CreateContrato(m, inp.ServicioIDs, inp.ServidorIDs, inp.ConxDbIDs, inp.AiConfigIDs); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Devolver el ID del contrato creado para que el frontend pueda enviar bienvenida
	created, _ := models.GetUltimoContratoByCliente(inp.ClienteID)
	contratoID := uint(0)
	if created != nil {
		contratoID = created.ID
	}
	return c.Status(201).JSON(fiber.Map{"message": "Contrato creado", "ok": true, "id": contratoID})
}

func UpdateContrato(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	type Input struct {
		ServicioIDs      []uint  `json:"servicio_ids"`
		Estado           string  `json:"estado"`
		FechaVencimiento string  `json:"fecha_vencimiento"`
		PrecioAcordado   float64 `json:"precio_acordado"`
		AutoRenovar      bool    `json:"auto_renovar"`
		Notas            string  `json:"notas"`
		ServidorIDs      []uint  `json:"servidor_ids"`
		ConxDbIDs        []uint  `json:"conx_db_ids"`
		AiConfigIDs      []uint  `json:"ai_config_ids"`
	}
	var inp Input
	if err := c.BodyParser(&inp); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	existing, err := models.GetContratoByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}
	if inp.FechaVencimiento != "" {
		fv, err := time.Parse("2006-01-02", inp.FechaVencimiento)
		if err == nil {
			existing.FechaVencimiento = fv
		}
	}
	if inp.Estado != "" {
		existing.Estado = inp.Estado
	}
	existing.PrecioAcordado = inp.PrecioAcordado
	existing.AutoRenovar = inp.AutoRenovar
	existing.Notas = inp.Notas
	if err := models.UpdateContrato(*existing, inp.ServicioIDs, inp.ServidorIDs, inp.ConxDbIDs, inp.AiConfigIDs); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Actualizado", "ok": true})
}

func RenovarContrato(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	existing, err := models.GetContratoByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}
	// Usar periodicidad del primer servicio asociado
	periodicidad := ""
	if len(existing.Servicios) > 0 {
		periodicidad = existing.Servicios[0].Periodicidad
	}
	nuevaInicio := existing.FechaVencimiento.AddDate(0, 0, 1)
	nuevaVenc := calcularFechaVencimiento(nuevaInicio, periodicidad)

	// Copiar IDs de servicios del contrato anterior
	var servicioIDs []uint
	for _, s := range existing.Servicios {
		servicioIDs = append(servicioIDs, s.ID)
	}
	nuevo := models.Contrato{
		ClienteID:        existing.ClienteID,
		FechaInicio:      nuevaInicio,
		FechaVencimiento: nuevaVenc,
		PrecioAcordado:   existing.PrecioAcordado,
		AutoRenovar:      existing.AutoRenovar,
		Estado:           "activo",
		Notas:            "Renovación automática desde contrato #" + strconv.Itoa(int(existing.ID)),
	}
	if err := models.CreateContrato(nuevo, servicioIDs, nil, nil, nil); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Marcar anterior como renovado
	existing.Estado = "renovado"
	models.UpdateContrato(*existing, nil, nil, nil, nil)

	return c.JSON(fiber.Map{"message": "Renovado", "ok": true})
}

func EnviarCorreoContrato(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	contrato, err := models.GetContratoByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}

	// Si se pasa regla_id, usar esa regla específica (EnviarNotificacionGrupo)
	var body struct {
		ReglaID uint `json:"regla_id"`
	}
	_ = c.BodyParser(&body)

	if body.ReglaID > 0 {
		regla, err := models.GetReglaByID(body.ReglaID)
		if err != nil {
			return c.Status(404).JSON(fiber.Map{"error": "Regla no encontrada"})
		}
		if err := services.EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}, ""); err != nil {
			return c.Status(500).JSON(fiber.Map{"error": err.Error()})
		}
		return c.JSON(fiber.Map{"message": "Correo enviado", "ok": true})
	}

	// Sin regla_id → flujo manual genérico
	if err := services.EnviarCorreoManual(contrato); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Correo enviado", "ok": true})
}

func DeleteContrato(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	if err := models.DeleteContrato(uint(id)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Eliminado", "ok": true})
}

// GetHistorialContrato devuelve la línea de tiempo de actividad de un contrato
func GetHistorialContrato(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	contrato, err := models.GetContratoByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}

	notifLogs, _ := models.GetLogsByContratoID(uint(id))
	pagoLogs, _ := models.GetDispatchLogsByContratoID(uint(id))

	// Construir timeline unificado
	type Evento struct {
		Tipo     string `json:"tipo"` // creacion | renovacion | notificacion | pago
		Icono    string `json:"icono"`
		Titulo   string `json:"titulo"`
		Detalle  string `json:"detalle"`
		Enlace   string `json:"enlace"` // URL de pago generada en este envío (opcional)
		Estado   string `json:"estado"` // ok | error | info
		FechaISO string `json:"fecha"`
	}

	var timeline []Evento

	// Evento: creación del contrato
	timeline = append(timeline, Evento{
		Tipo:     "creacion",
		Icono:    "document",
		Titulo:   "Contrato creado",
		Detalle:  "Inicio: " + contrato.FechaInicio.Format("02/01/2006") + " · Vence: " + contrato.FechaVencimiento.Format("02/01/2006"),
		Estado:   "info",
		FechaISO: contrato.CreatedAt.Format(time.RFC3339),
	})

	// Evento: pago confirmado (directo del contrato, siempre visible si se pagó)
	if contrato.PagoConfirmado && contrato.FechaPago != nil {
		timeline = append(timeline, Evento{
			Tipo:     "pago",
			Icono:    "currency",
			Titulo:   "Pago confirmado",
			Detalle:  "Nuevo vencimiento: " + contrato.FechaVencimiento.Format("02/01/2006"),
			Estado:   "ok",
			FechaISO: contrato.FechaPago.Format(time.RFC3339),
		})
	}

	// Evento: renovaciones (updated_at con estado renovado — heurístico por estado)
	if contrato.Estado == "renovado" {
		timeline = append(timeline, Evento{
			Tipo:     "renovacion",
			Icono:    "refresh",
			Titulo:   "Contrato renovado",
			Detalle:  "Nuevo vencimiento: " + contrato.FechaVencimiento.Format("02/01/2006"),
			Estado:   "ok",
			FechaISO: contrato.UpdatedAt.Format(time.RFC3339),
		})
	}

	// Eventos: notificaciones enviadas
	for _, n := range notifLogs {
		estado := "ok"
		if n.Estado == "fallido" {
			estado = "error"
		} else if n.Estado == "pendiente" {
			estado = "info"
		}
		detalle := n.Asunto
		if n.Estado == "fallido" && n.ErrorMsg != "" {
			detalle += " · Error: " + n.ErrorMsg
		}
		reglaLabel := ""
		if n.Regla.ID > 0 {
			reglaLabel = " (" + n.Regla.Nombre + ")"
		}
		timeline = append(timeline, Evento{
			Tipo:     "notificacion",
			Icono:    "mail",
			Titulo:   "Notificación enviada" + reglaLabel,
			Detalle:  detalle,
			Enlace:   n.EnlacePago,
			Estado:   estado,
			FechaISO: n.CreatedAt.Format(time.RFC3339),
		})
	}

	// Eventos: pagos / dispatches
	for _, p := range pagoLogs {
		estado := "ok"
		if p.Estado == "failed" {
			estado = "error"
		}
		detalle := p.Referencia
		if p.PayerEmail != "" {
			detalle += " · " + p.PayerEmail
		}
		if p.Fuente != "" {
			detalle += " · " + p.Fuente
		}
		timeline = append(timeline, Evento{
			Tipo:     "pago",
			Icono:    "currency",
			Titulo:   "Pago recibido",
			Detalle:  detalle,
			Estado:   estado,
			FechaISO: p.CreatedAt.Format(time.RFC3339),
		})
	}

	// Ordenar por fecha desc
	for i := 0; i < len(timeline)-1; i++ {
		for j := i + 1; j < len(timeline); j++ {
			if timeline[j].FechaISO > timeline[i].FechaISO {
				timeline[i], timeline[j] = timeline[j], timeline[i]
			}
		}
	}

	return c.JSON(fiber.Map{
		"contrato": fiber.Map{
			"id":      contrato.ID,
			"cliente": contrato.Cliente.Nombre,
			"estado":  contrato.Estado,
		},
		"timeline": timeline,
		"total":    len(timeline),
	})
}

func calcularFechaVencimiento(desde time.Time, periodicidad string) time.Time {
	switch periodicidad {
	case "mensual":
		return desde.AddDate(0, 1, 0)
	case "trimestral":
		return desde.AddDate(0, 3, 0)
	case "semestral":
		return desde.AddDate(0, 6, 0)
	case "anual":
		return desde.AddDate(1, 0, 0)
	default:
		return desde.AddDate(1, 0, 0)
	}
}

// VerificarPagoBold consulta la API de Bold para un contrato específico y confirma el pago si está pagado.
// POST /api/contratos/:id/verificar-pago
func VerificarPagoBold(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}

	contrato, err := models.GetContratoParaVerificacion(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "Contrato no encontrado"})
	}
	if contrato.PagoConfirmado {
		return c.JSON(fiber.Map{"ok": true, "confirmado": true, "mensaje": "El pago ya estaba confirmado"})
	}
	if contrato.EnlacePagoLinkID == "" {
		return c.JSON(fiber.Map{"ok": false, "confirmado": false, "mensaje": "Este contrato no tiene enlace de pago generado"})
	}

	boldCfg, err := models.GetBoldConfig()
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Sin configuración Bold activa"})
	}

	paid, paymentID, monto, err := services.CheckBoldLinkPaid(boldCfg, contrato.EnlacePagoLinkID)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Error consultando Bold: " + err.Error()})
	}
	if !paid {
		return c.JSON(fiber.Map{"ok": true, "confirmado": false, "mensaje": "El enlace aún no ha sido pagado"})
	}

	ref := "contrato-" + strconv.FormatUint(id, 10)
	if ok, _ := models.MarcarContratoPagado(uint(id)); ok {
		go services.EnviarCorreoConfirmacionPago(uint(id), "bold")
	}
	go models.EnrichBoldCallbackLog(ref, contrato.Cliente.Email, monto)

	notifID := "api-check-" + paymentID
	if paymentID != "" && !models.IsBoldNotificationDuplicate(notifID) {
		_ = models.SaveBoldWebhookLog(models.BoldWebhookLog{
			NotificationID: notifID,
			Tipo:           "API_CHECK",
			PaymentID:      paymentID,
			Referencia:     ref,
			PayerEmail:     contrato.Cliente.Email,
			Monto:          monto,
			Procesado:      true,
		})
	}

	return c.JSON(fiber.Map{"ok": true, "confirmado": true, "monto": monto, "mensaje": "¡Pago confirmado!"})
}

// MarcarPagoManual confirma manualmente el pago de un contrato sin pasar por Bold.
// Solo limpia el enlace y marca pago_confirmado; NO toca estado ni fecha_vencimiento
// porque el admin puede haberlos ajustado manualmente antes de confirmar el pago.
// POST /api/contratos/:id/marcar-pagado
func MarcarPagoManual(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	ok, err := models.ConfirmarPagoManual(uint(id))
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	if !ok {
		return c.JSON(fiber.Map{"ok": true, "mensaje": "El pago ya estaba confirmado"})
	}
	go services.EnviarCorreoConfirmacionPago(uint(id), "manual")
	return c.JSON(fiber.Map{"ok": true, "mensaje": "Pago marcado como confirmado"})
}

// GenerarDocumentoContrato produce el PDF del contrato (cláusulas estándar) a partir
// de la plantilla activa tipo 'contrato'. La lógica real vive en
// services.GenerarDocumentoContrato, compartida con la tool del agente.
// POST /api/v2/contratos/:id/generar-documento
func GenerarDocumentoContrato(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	doc, err := services.GenerarDocumentoContrato(uint(id), generadoPorFromContext(c))
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(fiber.Map{"ok": true, "documento": doc})
}
