package controllers

import (
	"crypto/subtle"
	"log"
	"regexp"
	"strconv"
	"strings"

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

// ─── Webhook de correo entrante ───────────────────────────────────────────────
// Recibe notificaciones de SendGrid, Mailgun, etc.
// POST /webhooks/soporte/:provider?key=<ApiKey de la config activa>
// provider: sendgrid, mailgun, generic

type soporteEmailIn struct {
	From      string `json:"from" form:"from"`
	Subject   string `json:"subject" form:"subject"`
	Text      string `json:"text" form:"text"`
	Html      string `json:"html" form:"html"`
	Sender    string `json:"sender" form:"sender"`
	FromName  string `json:"from_name" form:"from_name"`
	MessageID string `json:"message_id" form:"message_id"`
}

var ticketRefRe = regexp.MustCompile(`(?i)\[Ticket #(\d+)\]`)
var messageIDHeaderRe = regexp.MustCompile(`(?im)^Message-ID:\s*(<[^>\r\n]+>)`)

// validarWebhookKey compara la key recibida (query ?key= o header X-Webhook-Key /
// X-Api-Key) contra el ApiKey guardado en la config activa, en tiempo constante.
// Si la config no tiene ApiKey configurado, se rechaza toda petición: hay que
// definirlo en /app/soporte-webhook y usar esa misma URL con ?key=... en el proveedor.
func validarWebhookKey(c *fiber.Ctx, cfg *models.SoporteWebhookConfig) bool {
	if cfg.ApiKey == "" {
		return false
	}
	key := c.Query("key")
	if key == "" {
		key = c.Get("X-Webhook-Key")
	}
	if key == "" {
		key = c.Get("X-Api-Key")
	}
	if key == "" {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.ApiKey)) == 1
}

func SoporteWebhook(c *fiber.Ctx) error {
	provider := c.Params("provider", "generic")

	cfg, err := models.GetSoporteWebhookActivo()
	if err != nil {
		log.Printf("[SoporteWebhook] Sin config activa: %v", err)
		return c.Status(200).JSON(fiber.Map{"ok": false, "error": "sin config"})
	}

	if !validarWebhookKey(c, cfg) {
		log.Printf("[SoporteWebhook] Petición rechazada (key inválida o ausente) desde IP %s", c.IP())
		return c.Status(401).JSON(fiber.Map{"ok": false, "error": "no autorizado"})
	}

	var emails []soporteEmailIn

	switch provider {
	case "sendgrid":
		var sg struct {
			From     string `json:"from" form:"from"`
			Subject  string `json:"subject" form:"subject"`
			Text     string `json:"text" form:"text"`
			Html     string `json:"html" form:"html"`
			Sender   string `json:"sender" form:"sender"`
			FromName string `json:"from_name" form:"from_name"`
			Headers  string `json:"headers" form:"headers"`
		}
		if err := c.BodyParser(&sg); err != nil {
			return c.Status(200).JSON(fiber.Map{"ok": false, "error": "body inválido"})
		}
		emails = append(emails, soporteEmailIn{
			From: sg.From, Subject: sg.Subject, Text: sg.Text, Html: sg.Html,
			Sender: sg.Sender, FromName: sg.FromName, MessageID: extractMessageIDFromHeaders(sg.Headers),
		})
	case "mailgun":
		var mg struct {
			From      string `form:"from"`
			Subject   string `form:"subject"`
			Text      string `form:"body-plain"`
			Html      string `form:"body-html"`
			Sender    string `form:"sender"`
			FromName  string `form:"from_name"`
			MessageID string `form:"Message-Id"`
		}
		if err := c.BodyParser(&mg); err != nil {
			return c.Status(200).JSON(fiber.Map{"ok": false})
		}
		emails = append(emails, soporteEmailIn{
			From: mg.From, Subject: mg.Subject, Text: mg.Text,
			Html: mg.Html, Sender: mg.Sender, FromName: mg.FromName, MessageID: mg.MessageID,
		})
	default:
		var generic struct {
			From      string `json:"from"`
			Subject   string `json:"subject"`
			Text      string `json:"text"`
			Html      string `json:"html"`
			MessageID string `json:"message_id"`
		}
		if err := c.BodyParser(&generic); err != nil {
			return c.Status(200).JSON(fiber.Map{"ok": false})
		}
		emails = append(emails, soporteEmailIn{
			From: generic.From, Subject: generic.Subject, Text: generic.Text, Html: generic.Html,
			MessageID: generic.MessageID,
		})
	}

	for _, e := range emails {
		if e.From == "" || e.Subject == "" {
			continue
		}

		// Deduplicación: el proveedor puede reintentar la entrega del mismo correo.
		if models.EmailMessageIDYaProcesado(e.MessageID) {
			log.Printf("[SoporteWebhook] Correo duplicado ignorado (message_id=%s)", e.MessageID)
			continue
		}

		fromEmail := extractEmail(e.From)
		fromName := e.FromName
		if fromName == "" {
			fromName = extractName(e.From)
		}
		if fromName == "" {
			fromName = fromEmail
		}

		contenido := e.Text
		if contenido == "" {
			contenido = e.Html
		}
		contenido = strings.TrimSpace(contenido)
		if len(contenido) > 5000 {
			contenido = contenido[:5000]
		}

		// Enhebrado: si el asunto trae "[Ticket #N]" (lo agregamos nosotros en el
		// auto-ack) y ese ticket es del mismo remitente, es una respuesta — se
		// agrega como mensaje en vez de abrir un ticket nuevo. Si no hay token
		// pero el remitente tiene un ticket abierto reciente, también se enhebra.
		if hilo := buscarTicketDeHilo(fromEmail, e.Subject); hilo != nil {
			msg := &models.TicketMensaje{
				TicketID:    hilo.ID,
				Contenido:   contenido,
				EsAdmin:     false,
				AutorNombre: fromName,
				MessageID:   e.MessageID,
			}
			if err := models.CreateTicketMensaje(msg); err != nil {
				log.Printf("[SoporteWebhook] Error agregando mensaje al ticket #%d: %v", hilo.ID, err)
				continue
			}
			if hilo.Estado == "resuelto" || hilo.Estado == "cerrado" {
				_ = models.UpdateTicketEstado(hilo.ID, "abierto")
			}
			log.Printf("[SoporteWebhook] Respuesta agregada al ticket #%d (%s)", hilo.ID, fromEmail)
			continue
		}

		ticket := &models.ProyectoTicket{
			AutorNombre: fromName,
			EmailFrom:   fromEmail,
			Titulo:      e.Subject,
			Descripcion: contenido,
			Estado:      "abierto",
			Origen:      "email",
			MessageID:   e.MessageID,
		}
		if cfg.AsignarA != nil {
			ticket.AsignadoA = cfg.AsignarA
		}
		if err := models.CreateProyectoTicket(ticket); err != nil {
			log.Printf("[SoporteWebhook] Error creando ticket: %v", err)
			continue
		}
		log.Printf("[SoporteWebhook] Ticket #%d creado desde email (%s): %s", ticket.ID, fromEmail, e.Subject)

		// Notificar admin
		services.SendSoporteNotifAdmin(ticket)

		// Auto-responder (el asunto incluye [Ticket #N] para poder enhebrar la respuesta)
		if cfg.ResponderAuto {
			services.SendSoporteAutoRespuesta(ticket)
		}
	}

	return c.Status(200).JSON(fiber.Map{"ok": true})
}

// buscarTicketDeHilo intenta encontrar el ticket al que pertenece una respuesta:
// primero por el token "[Ticket #N]" en el asunto (verificando que sea del mismo
// remitente, para que nadie pueda inyectar mensajes en el ticket de otro
// adivinando el número), y si no hay token, por el último ticket abierto de ese
// remitente cuando el asunto tiene pinta de respuesta (Re:/RE:/Fwd:).
func buscarTicketDeHilo(fromEmail, subject string) *models.ProyectoTicket {
	if m := ticketRefRe.FindStringSubmatch(subject); m != nil {
		id, _ := strconv.ParseUint(m[1], 10, 32)
		if id > 0 {
			t, err := models.GetTicketByID(uint(id))
			if err == nil && strings.EqualFold(t.EmailFrom, fromEmail) {
				return t
			}
		}
	}
	lower := strings.ToLower(strings.TrimSpace(subject))
	if strings.HasPrefix(lower, "re:") || strings.HasPrefix(lower, "fwd:") || strings.HasPrefix(lower, "fw:") {
		if t, err := models.GetUltimoTicketAbiertoPorEmail(fromEmail); err == nil {
			return t
		}
	}
	return nil
}

func extractMessageIDFromHeaders(headers string) string {
	if headers == "" {
		return ""
	}
	if m := messageIDHeaderRe.FindStringSubmatch(headers); m != nil {
		return m[1]
	}
	return ""
}

// ─── Asignar ticket a usuario ─────────────────────────────────────────────────
// PUT /app/tickets/:ticketID/asignar

func AsignarTicket(c *fiber.Ctx) error {
	ticketID, _ := strconv.ParseUint(c.Params("ticketID"), 10, 32)
	type Req struct {
		AsignadoID uint `json:"asignado_id"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
	}
	if err := app.Http.Database.DB.Model(&models.ProyectoTicket{}).
		Where("id = ?", ticketID).
		Update("asignado_a", req.AsignadoID).Error; err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ─── Listar admins para asignación ────────────────────────────────────────────
// GET /app/tickets/admins

func GetSoporteAdmins(c *fiber.Ctx) error {
	var admins []models.Users
	app.Http.Database.DB.Where("is_admin = ?", true).
		Or("role_id IN (SELECT id FROM roles WHERE name = 'soporte' OR name = 'admin')").
		Find(&admins)
	type item struct {
		ID   uint   `json:"id"`
		Name string `json:"name"`
	}
	result := make([]item, 0)
	for _, a := range admins {
		result = append(result, item{ID: a.ID, Name: a.Name})
	}
	return c.JSON(result)
}

// ─── Configuración del webhook ────────────────────────────────────────────────

func SoporteWebhookConfigPage(c *fiber.Ctx) error {
	cfg, _ := models.GetSoporteWebhookActivo()
	return c.Render("soporte_webhook", fiber.Map{
		"user":    c.Locals("user"),
		"modules": c.Locals("userModules"),
		"cfg":     cfg,
	}, "layouts/main")
}

func GetSoporteWebhookConfig(c *fiber.Ctx) error {
	cfg, err := models.GetSoporteWebhookActivo()
	if err != nil {
		return c.JSON(fiber.Map{"data": nil})
	}
	return c.JSON(fiber.Map{"data": cfg})
}

func SaveSoporteWebhookConfig(c *fiber.Ctx) error {
	type body struct {
		ID             uint   `json:"id"`
		Nombre         string `json:"nombre"`
		Provider       string `json:"provider"`
		ApiKey         string `json:"api_key"`
		EmailDestino   string `json:"email_destino"`
		ResponderAuto  bool   `json:"responder_auto"`
		MensajeAuto    string `json:"mensaje_auto"`
		AsignarA       *uint  `json:"asignar_a"`
		SmtpHost       string `json:"smtp_host"`
		SmtpPort       int    `json:"smtp_port"`
		SmtpUsername   string `json:"smtp_username"`
		SmtpPassword   string `json:"smtp_password"`
		SmtpEncryption string `json:"smtp_encryption"`
		SmtpFromAddr   string `json:"smtp_from_addr"`
		SmtpFromName   string `json:"smtp_from_name"`
	}
	var b body
	if err := c.BodyParser(&b); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
	}
	port := b.SmtpPort
	if port == 0 {
		port = 587
	}
	enc := b.SmtpEncryption
	if enc == "" {
		enc = "starttls"
	}
	cfg := &models.SoporteWebhookConfig{
		Nombre:         b.Nombre,
		Provider:       b.Provider,
		ApiKey:         b.ApiKey,
		EmailDestino:   b.EmailDestino,
		ResponderAuto:  b.ResponderAuto,
		MensajeAuto:    b.MensajeAuto,
		AsignarA:       b.AsignarA,
		SmtpHost:       b.SmtpHost,
		SmtpPort:       port,
		SmtpUsername:   b.SmtpUsername,
		SmtpPassword:   b.SmtpPassword,
		SmtpEncryption: enc,
		SmtpFromAddr:   b.SmtpFromAddr,
		SmtpFromName:   b.SmtpFromName,
		Activo:         true,
	}
	cfg.ID = b.ID
	if err := models.SaveSoporteWebhookConfig(cfg); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ─── helpers ──────────────────────────────────────────────────────────────────

func extractEmail(s string) string {
	s = strings.TrimSpace(s)
	if idx := strings.LastIndex(s, "<"); idx >= 0 {
		s = s[idx+1:]
	}
	if idx := strings.LastIndex(s, ">"); idx >= 0 {
		s = s[:idx]
	}
	return strings.TrimSpace(s)
}

func extractName(s string) string {
	s = strings.TrimSpace(s)
	if idx := strings.Index(s, "<"); idx >= 0 {
		return strings.TrimSpace(s[:idx])
	}
	return ""
}
