package services

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"strconv"
	"time"

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

type cronProcInfo struct {
	Nombre string  `json:"nombre"`
	CPU    float64 `json:"cpu"`
	RAMMB  float64 `json:"ram_mb"`
}

var cronScheduler *cron.Cron

// IniciarCron arranca el scheduler de tareas. Llamar desde app.go o main.go.
func IniciarCron() {
	cronScheduler = cron.New()

	// Vencimientos próximos — todos los días a las 8:00 AM
	if _, err := cronScheduler.AddFunc("0 8 * * *", ProcesarVencimientosProximos); err != nil {
		log.Printf("[CRON] Error registrando tarea vencimientos_proximo: %v", err)
		return
	}

	// Contratos ya vencidos — todos los días a las 9:00 AM
	if _, err := cronScheduler.AddFunc("0 9 * * *", ProcesarYaVencidos); err != nil {
		log.Printf("[CRON] Error registrando tarea ya_vencido: %v", err)
		return
	}

	// Polling Bold — cada 15 minutos para detectar pagos sin webhook
	if _, err := cronScheduler.AddFunc("*/15 * * * *", VerificarPagosBoldPendientes); err != nil {
		log.Printf("[CRON] Error registrando tarea bold_polling: %v", err)
		return
	}

	// Salud de servidores — cada 5 minutos
	if _, err := cronScheduler.AddFunc("*/5 * * * *", VerificarSaludServidores); err != nil {
		log.Printf("[CRON] Error registrando tarea salud_servidores: %v", err)
		return
	}

	// Purga de historial de métricas — cada noche a las 3 AM (retención 7 días)
	if _, err := cronScheduler.AddFunc("0 3 * * *", func() {
		if err := models.PurgarMetricasHistory(7); err != nil {
			log.Printf("[CRON] Error purgando historial métricas: %v", err)
		} else {
			log.Println("[CRON] Historial de métricas purgado (>7 días)")
		}
		if err := models.PurgarUrlMonitorLogs(7); err != nil {
			log.Printf("[CRON] Error purgando logs URL monitor: %v", err)
		}
	}); err != nil {
		log.Printf("[CRON] Error registrando tarea purga_metricas: %v", err)
		return
	}

	// Monitor de URLs — cada minuto
	if _, err := cronScheduler.AddFunc("* * * * *", VerificarUrlMonitors); err != nil {
		log.Printf("[CRON] Error registrando tarea url_monitor: %v", err)
		return
	}

	cronScheduler.Start()
	log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM, Bold polling cada 15min, salud servidores cada 5min")
}

// DetenerCron para graceful shutdown
func DetenerCron() {
	if cronScheduler != nil {
		cronScheduler.Stop()
	}
}

// ProcesarVencimientos mantiene compatibilidad para llamadas manuales
func ProcesarVencimientos() {
	ProcesarVencimientosProximos()
	ProcesarYaVencidos()
}

// ProcesarVencimientosProximos procesa reglas de tipo "vencimiento_proximo"
func ProcesarVencimientosProximos() {
	log.Println("[CRON] Procesando vencimientos próximos...")

	reglas, err := models.GetReglasByTipoEvento("vencimiento_proximo")
	if err != nil {
		log.Printf("[CRON] Error obteniendo reglas vencimiento_proximo: %v", err)
		return
	}

	for _, regla := range reglas {
		contratos, err := models.GetContratosProximosVencer(regla.DiasAntes)
		if err != nil {
			log.Printf("[CRON] Error obteniendo contratos para regla %d: %v", regla.ID, err)
			continue
		}
		procesarContratos(&regla, contratos)
	}

	log.Println("[CRON] Vencimientos próximos finalizado")
}

// ProcesarYaVencidos procesa reglas de tipo "ya_vencido"
func ProcesarYaVencidos() {
	log.Println("[CRON] Procesando contratos ya vencidos...")

	reglas, err := models.GetReglasByTipoEvento("ya_vencido")
	if err != nil {
		log.Printf("[CRON] Error obteniendo reglas ya_vencido: %v", err)
		return
	}
	if len(reglas) == 0 {
		return
	}

	contratos, err := models.GetContratosYaVencidos()
	if err != nil {
		log.Printf("[CRON] Error obteniendo contratos vencidos: %v", err)
		return
	}

	for _, regla := range reglas {
		procesarContratos(&regla, contratos)
	}

	log.Println("[CRON] Ya vencidos finalizado")
}

// procesarContratos aplica filtros y envía notificaciones para una regla y lista de contratos
func procesarContratos(regla *models.NotificacionRegla, contratos []models.Contrato) {
	if len(contratos) == 0 {
		return
	}

	// Filtrar por AplicaA
	var filtrados []models.Contrato
	for _, c := range contratos {
		switch regla.AplicaA {
		case "renovable":
			for _, s := range c.Servicios {
				if s.Tipo == "renovable" {
					filtrados = append(filtrados, c)
					break
				}
			}
		case "unico":
			for _, s := range c.Servicios {
				if s.Tipo == "unico" {
					filtrados = append(filtrados, c)
					break
				}
			}
		default:
			filtrados = append(filtrados, c)
		}
	}
	if len(filtrados) == 0 {
		return
	}

	// Agrupar por cliente
	porCliente := make(map[uint][]models.Contrato)
	for _, c := range filtrados {
		porCliente[c.ClienteID] = append(porCliente[c.ClienteID], c)
	}

	for clienteID, grupoContratos := range porCliente {
		if models.YaEnviadoHoy(clienteID, regla.ID) {
			log.Printf("[CRON] Ya enviado hoy a cliente %d para regla %d — saltando", clienteID, regla.ID)
			continue
		}
		cliente := &grupoContratos[0].Cliente
		if err := EnviarNotificacionGrupo(regla, cliente, grupoContratos, ""); err != nil {
			log.Printf("[CRON] Error enviando a cliente %d: %v", clienteID, err)
		} else {
			log.Printf("[CRON] Enviado a cliente %d (%s) — %d contrato(s)", clienteID, cliente.Email, len(grupoContratos))
		}
	}
}

// VerificarPagosBoldPendientes consulta la API de Bold para cada contrato que tiene un
// enlace de pago generado pero aún no confirmado. Se ejecuta cada 15 minutos via cron.
// Esto compensa la ausencia de webhooks de Bold.
func VerificarPagosBoldPendientes() {
	contratos, err := models.GetContratosConEnlacePendiente()
	if err != nil {
		log.Printf("[BOLD-POLL] Error obteniendo contratos pendientes: %v", err)
		return
	}
	if len(contratos) == 0 {
		return
	}

	boldCfg, err := models.GetBoldConfig()
	if err != nil {
		log.Printf("[BOLD-POLL] Sin configuración Bold activa: %v", err)
		return
	}

	log.Printf("[BOLD-POLL] Verificando %d contrato(s) con enlace pendiente...", len(contratos))

	for _, c := range contratos {
		paid, paymentID, monto, err := CheckBoldLinkPaid(boldCfg, c.EnlacePagoLinkID)
		if err != nil {
			log.Printf("[BOLD-POLL] Error consultando link %s (contrato %d): %v", c.EnlacePagoLinkID, c.ID, err)
			continue
		}
		if !paid {
			continue
		}

		ref := fmt.Sprintf("contrato-%d", c.ID)
		log.Printf("[BOLD-POLL] Pago detectado para %s (payment_id=%s, monto=%d)", ref, paymentID, monto)

		if ok, _ := models.MarcarContratoPagado(c.ID); ok {
			go EnviarCorreoConfirmacionPago(c.ID, "bold")
			log.Printf("[BOLD-POLL] Contrato %d marcado como pagado", c.ID)
		}

		// Enriquecer callback log si existe
		go models.EnrichBoldCallbackLog(ref, c.Cliente.Email, monto)

		// Guardar en webhook log como API_CHECK para trazabilidad
		notifID := "api-check-" + paymentID
		if paymentID != "" && !models.IsBoldNotificationDuplicate(notifID) {
			_ = models.SaveBoldWebhookLog(models.BoldWebhookLog{
				NotificationID: notifID,
				Tipo:           "API_CHECK",
				PaymentID:      paymentID,
				Referencia:     ref,
				PayerEmail:     c.Cliente.Email,
				Monto:          monto,
				Procesado:      true,
			})
		}
	}

	log.Println("[BOLD-POLL] Verificación completada")
}

// ─── Salud de Servidores ─────────────────────────────────────────────────────

// VerificarSaludServidores corre cada 5 min y genera notificaciones del sistema
// cuando detecta: agente caído, recurso al límite o VPS próximo a vencer.
func VerificarSaludServidores() {
	cfg := models.GetServidorAlertaUmbral()
	if !cfg.Activo {
		return
	}

	servidores, _, err := models.GetAllServidores(1000, 0, "")
	if err != nil {
		log.Printf("[CRON-SRV] Error obteniendo servidores: %v", err)
		return
	}

	for _, srv := range servidores {
		// 1. Agente caído (sin ping en X minutos)
		if srv.AgentToken != "" && srv.AgentLastSeen != nil {
			minutos := time.Since(*srv.AgentLastSeen).Minutes()
			if minutos > float64(cfg.MinutosSinPing) {
				titulo := fmt.Sprintf("🔴 Sin señal: %s", srv.Nombre)
				if !models.YaExisteAlertaServidor(titulo, 1) {
					crearNotifServidor(titulo,
						fmt.Sprintf("El agente no reporta hace %.0f minutos (umbral: %d min)", minutos, cfg.MinutosSinPing),
						"servidor_caido")
				}
			}
		}

		// 2. Recursos (CPU / RAM / Disco) al límite
		if srv.MetricasJson != "" {
			var m struct {
				CPU    struct{ Porcentaje float64 `json:"porcentaje"` } `json:"cpu"`
				RAM    struct{ Porcentaje float64 `json:"porcentaje"` } `json:"ram"`
				Discos []struct {
					Porcentaje float64 `json:"porcentaje"`
					Ruta       string  `json:"ruta"`
				} `json:"discos"`
				TopProcs []cronProcInfo `json:"top_procs"`
			}
			if jsonErr := json.Unmarshal([]byte(srv.MetricasJson), &m); jsonErr == nil {
				checkRecurso(srv.Nombre, "CPU", m.CPU.Porcentaje, cfg.UmbralCPU, m.TopProcs, "cpu")
				checkRecurso(srv.Nombre, "RAM", m.RAM.Porcentaje, cfg.UmbralRAM, m.TopProcs, "ram")
				for _, d := range m.Discos {
					label := "Disco"
					if d.Ruta != "" && d.Ruta != "/" {
						label = "Disco " + d.Ruta
					}
					checkRecurso(srv.Nombre, label, d.Porcentaje, cfg.UmbralDisco, nil, "")
				}
			}
		}

		// 3. Vencimiento próximo
		if srv.Vencimiento != "" && cfg.DiasAnteVencimiento > 0 {
			vence, parseErr := time.Parse("2006-01-02", srv.Vencimiento)
			if parseErr == nil {
				dias := int(time.Until(vence).Hours() / 24)
				if dias >= 0 && dias <= cfg.DiasAnteVencimiento {
					titulo := fmt.Sprintf("📅 %s vence en %s", srv.Nombre, strconv.Itoa(dias)+" días")
					if dias == 0 {
						titulo = fmt.Sprintf("📅 %s vence HOY", srv.Nombre)
					}
					if !models.YaExisteAlertaServidor(titulo, 12) {
						crearNotifServidor(titulo,
							fmt.Sprintf("Fecha de vencimiento: %s. Renueva a tiempo.", srv.Vencimiento),
							"servidor_vence_pronto")
					}
				}
			}
		}
	}
}

func checkRecurso(nombre, recurso string, valor float64, umbral int, procs []cronProcInfo, ordenar string) {
	if valor < float64(umbral) {
		return
	}
	titulo := fmt.Sprintf("⚠️ %s alto en %s (%.0f%%)", recurso, nombre, valor)
	if !models.YaExisteAlertaServidor(titulo, 1) {
		cuerpo := fmt.Sprintf("%s al %.0f%% (umbral: %d%%)", recurso, valor, umbral)
		if len(procs) > 0 {
			cp := make([]cronProcInfo, len(procs))
			copy(cp, procs)
			if ordenar == "cpu" {
				sort.Slice(cp, func(i, j int) bool { return cp[i].CPU > cp[j].CPU })
			} else {
				sort.Slice(cp, func(i, j int) bool { return cp[i].RAMMB > cp[j].RAMMB })
			}
			if len(cp) > 3 {
				cp = cp[:3]
			}
			cuerpo += "\n\nTop procesos:"
			for i, p := range cp {
				if ordenar == "cpu" {
					cuerpo += fmt.Sprintf("\n%d. %s — CPU: %.1f%%, RAM: %.0f MB", i+1, p.Nombre, p.CPU, p.RAMMB)
				} else {
					cuerpo += fmt.Sprintf("\n%d. %s — RAM: %.0f MB, CPU: %.1f%%", i+1, p.Nombre, p.RAMMB, p.CPU)
				}
			}
		}
		crearNotifServidor(titulo, cuerpo, "servidor_recurso_alto")
	}
}

func crearNotifServidor(titulo, cuerpo, evento string) {
	// Verificar si la notificación del sistema está habilitada para este evento
	evtCfg := models.GetNotifConfig(evento, "admin")
	// Por defecto, si no hay config, crear notificación de sistema igualmente
	if evtCfg == nil || evtCfg.CanalSistema {
		_ = models.CreateSistemaNotif(&models.SistemaNotificacion{
			TipoUsuario: "admin",
			Titulo:      titulo,
			Cuerpo:      cuerpo,
			Url:         "/app/servidor-dashboard",
			Icono:       "🖥️",
		})
	}
	// Telegram (si está configurado para este evento)
	if evtCfg != nil && evtCfg.CanalTelegram {
		msg := fmt.Sprintf("<b>%s</b>\n%s", titulo, cuerpo)
		sendTelegramAdmin(msg)
	}
}

// ─── Monitor de URLs ──────────────────────────────────────────────────────────

func VerificarUrlMonitors() {
	monitors, err := models.GetActiveUrlMonitors()
	if err != nil || len(monitors) == 0 {
		return
	}
	now := time.Now()
	for _, m := range monitors {
		m := m
		if m.UltimoCheckAt != nil {
			if now.Sub(*m.UltimoCheckAt).Minutes() < float64(m.IntervaloMin) {
				continue
			}
		}
		go ejecutarChequeoURL(m)
	}
}

func EjecutarChequeoURLPublic(m models.UrlMonitor) { ejecutarChequeoURL(m) }

func ejecutarChequeoURL(m models.UrlMonitor) {
	statusCode, latMs, err := doHttpCheck(m.URL, m.TimeoutSeg)
	ok := err == nil && statusCode >= 200 && statusCode < 400
	errStr := ""
	if err != nil {
		errStr = err.Error()
	}

	anteriorOk := m.UltimoOk == nil || *m.UltimoOk

	models.UpdateUrlMonitorStatus(m.ID, statusCode, latMs, ok)
	models.InsertUrlMonitorLog(m.ID, statusCode, latMs, ok, errStr)

	if m.UltimoOk == nil {
		return // primer chequeo, no alertar
	}
	if !ok && anteriorOk {
		detalle := errStr
		if detalle == "" {
			detalle = fmt.Sprintf("HTTP %d", statusCode)
		}
		msg := fmt.Sprintf("🔴 <b>Sitio caído</b>\n<b>%s</b>\n%s\n\nError: %s",
			escapeTelegramHTML(m.Nombre),
			escapeTelegramHTML(m.URL),
			escapeTelegramHTML(detalle))
		sendTelegramAdmin(msg)
		log.Printf("[URL-MON] DOWN %s — %s", m.Nombre, detalle)
	} else if ok && !anteriorOk {
		msg := fmt.Sprintf("✅ <b>Sitio recuperado</b>\n<b>%s</b>\n%s\nLatencia: <b>%dms</b>",
			escapeTelegramHTML(m.Nombre),
			escapeTelegramHTML(m.URL),
			latMs)
		sendTelegramAdmin(msg)
		log.Printf("[URL-MON] UP %s — %dms", m.Nombre, latMs)
	}
}

func doHttpCheck(url string, timeoutSeg int) (int, int64, error) {
	if timeoutSeg <= 0 {
		timeoutSeg = 10
	}
	client := &http.Client{
		Timeout: time.Duration(timeoutSeg) * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			if len(via) >= 5 {
				return fmt.Errorf("demasiadas redirecciones")
			}
			return nil
		},
	}

	start := time.Now()
	req, err := http.NewRequest(http.MethodHead, url, nil)
	if err != nil {
		return 0, 0, err
	}
	req.Header.Set("User-Agent", "usite-monitor/1.0")

	resp, err := client.Do(req)
	latMs := time.Since(start).Milliseconds()
	if err != nil || (resp != nil && resp.StatusCode == http.StatusMethodNotAllowed) {
		if resp != nil {
			resp.Body.Close()
		}
		// Reintentar con GET
		start = time.Now()
		req2, _ := http.NewRequest(http.MethodGet, url, nil)
		req2.Header.Set("User-Agent", "usite-monitor/1.0")
		resp2, err2 := client.Do(req2)
		latMs = time.Since(start).Milliseconds()
		if err2 != nil {
			return 0, latMs, err2
		}
		resp2.Body.Close()
		return resp2.StatusCode, latMs, nil
	}
	resp.Body.Close()
	return resp.StatusCode, latMs, nil
}
