package models

import (
	"log"
	"strings"
	"time"

	"github.com/sujit-baniya/fiber-boilerplate/app"
	"gorm.io/gorm"
)

type Contrato struct {
	gorm.Model
	ClienteID        uint       `json:"cliente_id" gorm:"column:cliente_id"`
	Cliente          Cliente    `json:"cliente" gorm:"foreignKey:ClienteID"`
	Servicios        []Servicio `json:"servicios" gorm:"many2many:contrato_servicios"`
	FechaInicio      time.Time  `json:"fecha_inicio" gorm:"column:fecha_inicio"`
	FechaVencimiento time.Time  `json:"fecha_vencimiento" gorm:"column:fecha_vencimiento"`
	PrecioAcordado   float64    `json:"precio_acordado" gorm:"column:precio_acordado"`
	Moneda           string     `json:"moneda"           gorm:"column:moneda;default:'COP'"`
	Estado           string     `json:"estado" gorm:"column:estado;default:'activo'"` // activo | vencido | cancelado | renovado
	AutoRenovar      bool       `json:"auto_renovar" gorm:"column:auto_renovar;default:false"`
	Notas            string     `json:"notas" gorm:"column:notas"`
	// Infraestructura asociada (opcional, múltiple)
	Servidores []Servidor `json:"servidores" gorm:"many2many:contrato_servidores"`
	ConxDBs    []ConxDb   `json:"conx_dbs" gorm:"many2many:contrato_conx_dbs"`
	AiConfigs  []AiConfig `json:"ai_configs" gorm:"many2many:contrato_ai_configs"`
	// Enlace de pago Bold: único por ciclo de pago.
	// Se reutiliza en múltiples notificaciones del mismo ciclo.
	// Se anula (vacía) cuando SALE_APPROVED llega y se registra el pago.
	EnlacePago       string `json:"enlace_pago"         gorm:"column:enlace_pago;type:text"`
	EnlacePagoLinkID string `json:"enlace_pago_link_id"  gorm:"column:enlace_pago_link_id;type:varchar(64)"`
	// Confirmación de pago
	PagoConfirmado bool       `json:"pago_confirmado" gorm:"column:pago_confirmado;default:false"`
	FechaPago      *time.Time `json:"fecha_pago"       gorm:"column:fecha_pago"`
}

func (Contrato) TableName() string { return "contratos" }

func GetAllContratos(limit, offset int, search, estado string) ([]Contrato, int64, error) {
	var items []Contrato
	var total int64
	db := app.Http.Database.DB.Model(&Contrato{}).
		Preload("Cliente").Preload("Servicios").
		Preload("Servidores").Preload("ConxDBs").Preload("AiConfigs")
	if search != "" {
		db = db.Joins("JOIN clientes ON clientes.id = contratos.cliente_id").
			Where("clientes.nombre ILIKE ? OR clientes.empresa ILIKE ?",
				"%"+search+"%", "%"+search+"%")
	}
	if estado != "" {
		db = db.Where("contratos.estado = ?", estado)
	}
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if err := db.Order("contratos.fecha_vencimiento ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

func GetContratoByID(id uint) (*Contrato, error) {
	var item Contrato
	if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").Preload("Servidores").Preload("ConxDBs").Preload("AiConfigs").First(&item, id).Error; err != nil {
		return nil, err
	}
	return &item, nil
}

// GetUltimoContratoByCliente devuelve el contrato más reciente de un cliente
func GetUltimoContratoByCliente(clienteID uint) (*Contrato, error) {
	var item Contrato
	if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").
		Where("cliente_id = ?", clienteID).
		Order("id DESC").First(&item).Error; err != nil {
		return nil, err
	}
	return &item, nil
}

// GetContratosByCliente devuelve contratos de un cliente específico
func GetContratosByCliente(clienteID uint) ([]Contrato, error) {
	var items []Contrato
	if err := app.Http.Database.DB.Preload("Servicios").
		Where("cliente_id = ?", clienteID).
		Order("fecha_vencimiento ASC").Find(&items).Error; err != nil {
		return nil, err
	}
	return items, nil
}

// GetContratosProximosVencer retorna contratos activos que vencen exactamente en N días
func GetContratosProximosVencer(diasAntes int) ([]Contrato, error) {
	var items []Contrato
	target := time.Now().AddDate(0, 0, diasAntes).UTC()
	startOfDay := time.Date(target.Year(), target.Month(), target.Day(), 0, 0, 0, 0, time.UTC)
	endOfDay := startOfDay.Add(24 * time.Hour)

	if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").
		Where("estado = 'activo' AND fecha_vencimiento >= ? AND fecha_vencimiento < ?", startOfDay, endOfDay).
		Find(&items).Error; err != nil {
		log.Printf("Error getting contratos proximos: %v", err)
		return nil, err
	}
	return items, nil
}

// GetContratosYaVencidos retorna contratos cuya fecha de vencimiento es hoy o anterior
// y su estado sigue siendo 'activo' (aún no han sido marcados como vencidos)
func GetContratosYaVencidos() ([]Contrato, error) {
	var items []Contrato
	hoy := time.Now().UTC()
	startOfDay := time.Date(hoy.Year(), hoy.Month(), hoy.Day(), 0, 0, 0, 0, time.UTC)

	if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").
		Where("estado = 'activo' AND fecha_vencimiento < ?", startOfDay).
		Find(&items).Error; err != nil {
		log.Printf("Error getting contratos vencidos: %v", err)
		return nil, err
	}
	return items, nil
}

// GetContratosConEnlacePendiente devuelve contratos que tienen un enlace de pago Bold generado
// pero cuyo pago aún no ha sido confirmado. Usado para polling periódico de la API de Bold.
func GetContratosConEnlacePendiente() ([]Contrato, error) {
	var items []Contrato
	if err := app.Http.Database.DB.Preload("Cliente").
		Where("enlace_pago_link_id != '' AND pago_confirmado = false AND deleted_at IS NULL").
		Find(&items).Error; err != nil {
		return nil, err
	}
	return items, nil
}

// syncContratoServicios sincroniza la tabla join contrato_servicios usando SQL directo
// para evitar que GORM intente hacer upsert de los servicios existentes.
func syncContratoServicios(contratoID uint, servicioIDs []uint) error {
	db := app.Http.Database.DB
	if err := db.Exec("DELETE FROM contrato_servicios WHERE contrato_id = ?", contratoID).Error; err != nil {
		return err
	}
	for _, sid := range servicioIDs {
		if err := db.Exec(
			"INSERT INTO contrato_servicios (contrato_id, servicio_id) VALUES (?, ?) ON CONFLICT DO NOTHING",
			contratoID, sid,
		).Error; err != nil {
			return err
		}
	}
	return nil
}

func syncContratoJoin(db *gorm.DB, table string, contratoID uint, ids []uint) error {
	if err := db.Exec("DELETE FROM "+table+" WHERE contrato_id = ?", contratoID).Error; err != nil {
		return err
	}
	// Derive FK column: "contrato_servidores" → "servidor_id", "contrato_conx_dbs" → "conx_db_id"
	entityPlural := strings.TrimPrefix(table, "contrato_")
	fkCol := strings.TrimSuffix(entityPlural, "s") + "_id"
	for _, id := range ids {
		if err := db.Exec("INSERT INTO "+table+" (contrato_id, "+fkCol+") VALUES (?, ?) ON CONFLICT DO NOTHING",
			contratoID, id,
		).Error; err != nil {
			return err
		}
	}
	return nil
}

func CreateContrato(c Contrato, servicioIDs, servidorIDs, conxDBIDs, aiConfigIDs []uint) error {
	db := app.Http.Database.DB
	if err := db.Omit("Servicios.*", "Servidores.*", "ConxDBs.*", "AiConfigs.*").Create(&c).Error; err != nil {
		return err
	}
	if err := syncContratoServicios(c.ID, servicioIDs); err != nil {
		return err
	}
	if err := syncContratoJoin(db, "contrato_servidores", c.ID, servidorIDs); err != nil {
		return err
	}
	if err := syncContratoJoin(db, "contrato_conx_dbs", c.ID, conxDBIDs); err != nil {
		return err
	}
	return syncContratoJoin(db, "contrato_ai_configs", c.ID, aiConfigIDs)
}

func UpdateContrato(c Contrato, servicioIDs, servidorIDs, conxDBIDs, aiConfigIDs []uint) error {
	db := app.Http.Database.DB
	if err := db.Model(&Contrato{}).Where("id = ?", c.ID).Updates(map[string]interface{}{
		"cliente_id":        c.ClienteID,
		"fecha_inicio":      c.FechaInicio,
		"fecha_vencimiento": c.FechaVencimiento,
		"precio_acordado":   c.PrecioAcordado,
		"moneda":            c.Moneda,
		"estado":            c.Estado,
		"auto_renovar":      c.AutoRenovar,
		"notas":             c.Notas,
	}).Error; err != nil {
		return err
	}
	if servicioIDs != nil {
		if err := syncContratoServicios(c.ID, servicioIDs); err != nil {
			return err
		}
	}
	if servidorIDs != nil {
		if err := syncContratoJoin(db, "contrato_servidores", c.ID, servidorIDs); err != nil {
			return err
		}
	}
	if conxDBIDs != nil {
		if err := syncContratoJoin(db, "contrato_conx_dbs", c.ID, conxDBIDs); err != nil {
			return err
		}
	}
	if aiConfigIDs != nil {
		if err := syncContratoJoin(db, "contrato_ai_configs", c.ID, aiConfigIDs); err != nil {
			return err
		}
	}
	return nil
}

func DeleteContrato(id uint) error {
	db := app.Http.Database.DB
	var c Contrato
	if err := db.First(&c, id).Error; err != nil {
		return err
	}
	db.Exec("DELETE FROM contrato_servicios WHERE contrato_id = ?", id)
	db.Exec("DELETE FROM contrato_servidores WHERE contrato_id = ?", id)
	db.Exec("DELETE FROM contrato_conx_dbs WHERE contrato_id = ?", id)
	db.Exec("DELETE FROM contrato_ai_configs WHERE contrato_id = ?", id)
	return db.Delete(&c).Error
}

// GuardarEnlacePago persiste el enlace de pago Bold en el contrato.
func GuardarEnlacePago(contratoID uint, linkID, url string) error {
	return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", contratoID).Updates(map[string]interface{}{
		"enlace_pago":         url,
		"enlace_pago_link_id": linkID,
	}).Error
}

// LimpiarEnlacePago borra el enlace vigente cuando se registra un pago aprobado,
// para que la próxima notificación genere un link nuevo.
func LimpiarEnlacePago(contratoID uint) error {
	return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", contratoID).Updates(map[string]interface{}{
		"enlace_pago":         "",
		"enlace_pago_link_id": "",
	}).Error
}

// periodicidadAMeses convierte el string de periodicidad en número de meses.
func periodicidadAMeses(p string) int {
	switch p {
	case "mensual":
		return 1
	case "trimestral":
		return 3
	case "semestral":
		return 6
	case "anual":
		return 12
	default:
		return 12
	}
}

// MarcarContratoPagado marca el contrato como pagado y renueva la fecha de vencimiento.
// Retorna (true, nil) si el contrato pasó de no-pagado a pagado en esta llamada.
// Retorna (false, nil) si ya estaba pagado (idempotente).
// WHERE pago_confirmado = false garantiza atomicidad ante llamadas concurrentes.
func MarcarContratoPagado(contratoID uint) (bool, error) {
	now := time.Now()

	// Cargar contrato con servicios para calcular nueva fecha de vencimiento
	var c Contrato
	if err := app.Http.Database.DB.Preload("Servicios").First(&c, contratoID).Error; err != nil {
		log.Printf("[PAGO] No se pudo cargar contrato %d para renovar: %v", contratoID, err)
		// Fallback: solo marcar pago sin renovar fecha
		result := app.Http.Database.DB.Model(&Contrato{}).
			Where("id = ? AND pago_confirmado = false", contratoID).
			Updates(map[string]interface{}{
				"pago_confirmado":     true,
				"fecha_pago":          now,
				"enlace_pago":         "",
				"enlace_pago_link_id": "",
			})
		return result.RowsAffected > 0, result.Error
	}

	// Ya estaba pagado — nada que hacer
	if c.PagoConfirmado {
		return false, nil
	}

	updates := map[string]interface{}{
		"pago_confirmado":     true,
		"fecha_pago":          now,
		"enlace_pago":         "",
		"enlace_pago_link_id": "",
		"estado":              "activo",
	}

	// Buscar el servicio renovable con mayor período para calcular nueva fecha
	meses := 0
	for _, s := range c.Servicios {
		if s.Tipo == "renovable" {
			if m := periodicidadAMeses(s.Periodicidad); m > meses {
				meses = m
			}
		}
	}
	if meses > 0 {
		// Extender desde la fecha de vencimiento actual; si ya venció, extender desde hoy
		base := c.FechaVencimiento
		if base.Before(now) {
			base = now
		}
		nuevaFecha := base.AddDate(0, meses, 0)
		updates["fecha_vencimiento"] = nuevaFecha
		log.Printf("[PAGO] Contrato %d renovado: %s → %s (%d meses)",
			contratoID,
			c.FechaVencimiento.Format("02/01/2006"),
			nuevaFecha.Format("02/01/2006"),
			meses,
		)
	}

	result := app.Http.Database.DB.Model(&Contrato{}).
		Where("id = ? AND pago_confirmado = false", contratoID).
		Updates(updates)
	return result.RowsAffected > 0, result.Error
}

// ConfirmarPagoManual marca el pago como confirmado sin modificar estado ni fecha_vencimiento.
// Útil cuando el admin ya ajustó manualmente el contrato y solo necesita limpiar el enlace pendiente.
func ConfirmarPagoManual(contratoID uint) (bool, error) {
	now := time.Now()
	result := app.Http.Database.DB.Model(&Contrato{}).
		Where("id = ? AND pago_confirmado = false", contratoID).
		Updates(map[string]interface{}{
			"pago_confirmado":     true,
			"fecha_pago":          now,
			"enlace_pago":         "",
			"enlace_pago_link_id": "",
		})
	return result.RowsAffected > 0, result.Error
}

// GetEstadoPago devuelve si el contrato tiene pago confirmado (para polling desde el frontend).
func GetEstadoPago(contratoID uint) (bool, *time.Time, error) {
	var c Contrato
	if err := app.Http.Database.DB.Select("pago_confirmado", "fecha_pago").First(&c, contratoID).Error; err != nil {
		return false, nil, err
	}
	return c.PagoConfirmado, c.FechaPago, nil
}

// GetContratoParaVerificacion devuelve los campos necesarios para verificar el pago directamente
// con la pasarela (pago_confirmado, enlace_pago_link_id). Preload de Cliente para poder usar el email.
func GetContratoParaVerificacion(contratoID uint) (*Contrato, error) {
	var c Contrato
	if err := app.Http.Database.DB.
		Preload("Cliente").
		Select("contratos.id", "contratos.pago_confirmado", "contratos.fecha_pago", "contratos.enlace_pago_link_id", "contratos.cliente_id").
		First(&c, contratoID).Error; err != nil {
		return nil, err
	}
	return &c, nil
}
