package controllers

import (
	"fmt"
	"strings"
	"unicode"

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

// sanitizeCFToken limpia el token antes de usarlo como Bearer en Cloudflare.
// Cubre los casos más comunes de pegado incorrecto desde el dashboard:
//   - BOM UTF-8
//   - Prefijo "Bearer " copiado accidentalmente
//   - Comillas simples/dobles alrededor del token
//   - Espacios y caracteres de control (tabulaciones, saltos de línea)
//   - Caracteres fuera del rango ASCII imprimible (U+0021–U+007E)
func sanitizeCFToken(s string) string {
	// 1. BOM UTF-8
	s = strings.TrimPrefix(s, "\xef\xbb\xbf")
	// 2. Espacios al inicio/fin (cubre paste con espacio visible)
	s = strings.TrimSpace(s)
	// 3. Prefijo "Bearer " copiado accidentalmente (case-insensitive)
	if after, ok := strings.CutPrefix(strings.ToLower(s), "bearer "); ok {
		s = s[len(s)-len(after):] // preservar casing original del token
	}
	s = strings.TrimSpace(s)
	// 4. Comillas envolventes (simples o dobles)
	if len(s) >= 2 {
		if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') {
			s = s[1 : len(s)-1]
		}
	}
	// 5. Solo ASCII imprimible (U+0021–U+007E), descarta control chars y unicode
	var b strings.Builder
	for _, r := range s {
		if r > 32 && r < 127 && unicode.IsPrint(r) {
			b.WriteRune(r)
		}
	}
	return b.String()
}

// ─── Configuración ────────────────────────────────────────────────────────────

// CloudflareConfigPage renderiza la vista de gestión de Cloudflare.
func CloudflareConfigPage(c *fiber.Ctx) error {
	cfg, _ := models.GetCloudflareConfig()
	if err := c.Render("cloudflare", fiber.Map{
		"Title":   "Cloudflare API",
		"Config":  cfg,
		"user":    c.Locals("user"),
		"modules": c.Locals("userModules"),
	}, "layouts/main"); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return nil
}

// SaveCloudflareConfig guarda o actualiza las credenciales de Cloudflare.
func SaveCloudflareConfig(c *fiber.Ctx) error {
	type body struct {
		ID        uint   `json:"id"        form:"id"`
		AuthType  string `json:"auth_type" form:"auth_type"` // "token" o "global_key"
		APIToken  string `json:"api_token" form:"api_token"`
		Email     string `json:"email"     form:"email"`
		AccountID string `json:"account_id" form:"account_id"`
		Nota      string `json:"nota"      form:"nota"`
	}
	var b body
	if err := c.BodyParser(&b); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	if b.APIToken == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "api_token requerido"})
	}
	if b.AuthType == "" {
		b.AuthType = "token"
	}
	if b.AuthType == "global_key" && b.Email == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "email requerido para auth_type=global_key"})
	}

	cfg := models.CloudflareConfig{
		AuthType:  b.AuthType,
		APIToken:  sanitizeCFToken(b.APIToken),
		Email:     strings.TrimSpace(b.Email),
		AccountID: sanitizeCFToken(b.AccountID),
		Nota:      b.Nota,
	}
	cfg.ID = b.ID

	if err := models.SaveCloudflareConfig(cfg); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Configuración guardada"})
}

// ─── Helper ───────────────────────────────────────────────────────────────────

// CleanCloudflareToken re-guarda el token activo aplicando saneamiento.
// Útil cuando el token fue guardado antes del fix de caracteres invisibles.
func CleanCloudflareToken(c *fiber.Ctx) error {
	cfg, err := models.GetCloudflareConfig()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Sin configuración activa"})
	}
	before := len([]rune(cfg.APIToken))
	cfg.APIToken = sanitizeCFToken(cfg.APIToken)
	cfg.AccountID = sanitizeCFToken(cfg.AccountID)
	cfg.Email = strings.TrimSpace(cfg.Email)
	after := len([]rune(cfg.APIToken))
	if err := models.SaveCloudflareConfig(*cfg); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{
		"message":      "Token re-guardado limpio",
		"chars_before": before,
		"chars_after":  after,
		"chars_removed": before - after,
	})
}

func cloudflareClient() (*services.CloudflareClient, error) {
	cfg, err := models.GetCloudflareConfig()
	if err != nil {
		return nil, err
	}
	if cfg.AuthType == "global_key" {
		return services.NewCloudflareClientGlobalKey(
			strings.TrimSpace(cfg.Email),
			sanitizeCFToken(cfg.APIToken),
			sanitizeCFToken(cfg.AccountID),
		), nil
	}
	return services.NewCloudflareClient(sanitizeCFToken(cfg.APIToken), sanitizeCFToken(cfg.AccountID)), nil
}

// VerifyCloudflareToken verifica las credenciales y devuelve sus permisos reales.
func VerifyCloudflareToken(c *fiber.Ctx) error {
	cfg, err := models.GetCloudflareConfig()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"ok":    false,
			"error": "No hay configuración activa de Cloudflare",
		})
	}

	raw := cfg.APIToken
	token := sanitizeCFToken(raw)
	if token == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ok": false, "error": "Token vacío"})
	}
	dirty := raw != token
	runes := []rune(token)
	preview := token
	if len(runes) > 14 {
		preview = string(runes[:6]) + "…" + string(runes[len(runes)-6:])
	}

	isGlobal := cfg.AuthType == "global_key"

	// Diagnóstico de formato según tipo
	var tokenDiag fiber.Map
	if isGlobal {
		hintFormat := ""
		if len(token) != 37 {
			hintFormat = fmt.Sprintf(
				"Longitud %d ≠ 37. La Global API Key tiene exactamente 37 caracteres hexadecimales.",
				len(token),
			)
		}
		tokenDiag = fiber.Map{
			"auth_type":       "global_key",
			"length":          len(token),
			"expected_length": "37 caracteres (Global API Key)",
			"preview":         preview,
			"was_sanitized":   dirty,
			"email":           cfg.Email,
			"hint_format":     hintFormat,
		}
	} else {
		hintFormat := ""
		if len(token) != 40 {
			hintFormat = fmt.Sprintf(
				"Longitud %d ≠ 40. Posible token incompleto, con prefijo extra, o tipo incorrecto (¿estás usando Global API Key en vez de API Token?)",
				len(token),
			)
		}
		tokenDiag = fiber.Map{
			"auth_type":       "token",
			"length":          len(token),
			"expected_length": "40 caracteres (API Token estándar)",
			"preview":         preview,
			"was_sanitized":   dirty,
			"raw_length":      len([]rune(raw)),
			"hint_format":     hintFormat,
		}
	}

	var client *services.CloudflareClient
	if isGlobal {
		client = services.NewCloudflareClientGlobalKey(
			strings.TrimSpace(cfg.Email),
			token,
			sanitizeCFToken(cfg.AccountID),
		)
	} else {
		client = services.NewCloudflareClient(token, sanitizeCFToken(cfg.AccountID))
	}

	// ── Para Global API Key: no existe /user/tokens/verify, verificamos con /user ──
	if isGlobal {
		user, err := client.GetUser()
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
				"ok":    false,
				"step":  "get_user",
				"error": err.Error(),
				"token": tokenDiag,
				"hint":  "Verifica que el email y la Global API Key sean correctos. La Global API Key se encuentra en dash.cloudflare.com → Mi Perfil → API Tokens → Global API Key → View.",
			})
		}

		zones, zonesErr := client.GetZones()
		zonesOK := zonesErr == nil
		zonesErrMsg := ""
		if zonesErr != nil {
			zonesErrMsg = zonesErr.Error()
		}

		dnsOK := false
		dnsErrMsg := ""
		dnsZoneID := ""
		if zonesOK && len(zones) > 0 {
			dnsZoneID = zones[0].ID
			_, dnsErr := client.GetDNSRecords(dnsZoneID)
			dnsOK = dnsErr == nil
			if dnsErr != nil {
				dnsErrMsg = dnsErr.Error()
			}
		}

		return c.JSON(fiber.Map{
			"ok":           true,
			"auth_type":    "global_key",
			"user_email":   user.Email,
			"user_id":      user.ID,
			"token":        tokenDiag,
			"permissions_test": fiber.Map{
				"zone_read": fiber.Map{"ok": zonesOK, "error": zonesErrMsg},
				"dns_read":  fiber.Map{"ok": dnsOK, "error": dnsErrMsg, "zone_tested": dnsZoneID},
			},
		})
	}

	// ── API Token normal ──
	verify, err := client.VerifyToken()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"ok":    false,
			"step":  "verify_token",
			"error": err.Error(),
			"token": tokenDiag,
			"hint":  "Ve a dash.cloudflare.com → Mi Perfil → API Tokens → crea un token nuevo con permisos Zone:Read y DNS:Read/Write",
		})
	}

	// 2. Obtener políticas reales del token
	detail, detailErr := client.GetTokenDetail(verify.ID)
	detailErrMsg := ""
	if detailErr != nil {
		detailErrMsg = detailErr.Error()
	}

	// 3. Probar Zone:Zone:Read
	zones, zonesErr := client.GetZones()
	zonesOK := zonesErr == nil
	zonesErrMsg := ""
	if zonesErr != nil {
		zonesErrMsg = zonesErr.Error()
	}

	// 4. Probar Zone:DNS:Read en la primera zona
	dnsOK := false
	dnsErrMsg := ""
	dnsZoneID := ""
	if zonesOK && len(zones) > 0 {
		dnsZoneID = zones[0].ID
		_, dnsErr := client.GetDNSRecords(dnsZoneID)
		dnsOK = dnsErr == nil
		if dnsErr != nil {
			dnsErrMsg = dnsErr.Error()
		}
	}

	hint := "Token válido con todos los permisos necesarios"
	if !zonesOK {
		hint = "Falta permiso Zone:Zone:Read"
	} else if !dnsOK {
		hint = "Falta permiso Zone:DNS:Read — o el token tiene scope de zonas específicas y no incluye la zona consultada. En Cloudflare: edita el token → Zone Resources → cambia a 'All zones'."
	}

	return c.JSON(fiber.Map{
		"ok":           true,
		"token_status": verify.Status,
		"token_id":     verify.ID,
		"token_len":    len(token),
		"token_prev":   preview,
		"was_dirty":    dirty,
		"detail_error": detailErrMsg,
		"policies":     func() interface{} { if detail != nil { return detail.Policies }; return nil }(),
		"permissions_test": fiber.Map{
			"zone_read":    fiber.Map{"ok": zonesOK, "error": zonesErrMsg},
			"dns_read":     fiber.Map{"ok": dnsOK, "error": dnsErrMsg, "zone_tested": dnsZoneID},
		},
		"hint": hint,
	})
}

// ─── Endpoints de datos ───────────────────────────────────────────────────────

// GetCloudflareUser devuelve la información del usuario autenticado en Cloudflare.
func GetCloudflareUser(c *fiber.Ctx) error {
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	data, err := client.GetUser()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": data})
}

// GetCloudflareZones lista todas las zonas (dominios) de la cuenta.
func GetCloudflareZones(c *fiber.Ctx) error {
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	data, err := client.GetZones()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": data})
}

// GetCloudflareDNS devuelve los registros DNS de una zona (:zone_id).
func GetCloudflareDNS(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	if zoneID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	data, err := client.GetDNSRecords(zoneID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": data, "zone_id": zoneID})
}

// GetCloudflareSSL devuelve los certificados SSL de una zona (:zone_id).
// Intenta primero certificate_packs?status=all; si falla (plan Free o sin ACM)
// devuelve el estado de Universal SSL como fallback.
func GetCloudflareSSL(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	if zoneID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}

	// 1. Intentar certificate_packs?status=all
	packs, packsErr := client.GetSSLCertificates(zoneID)
	if packsErr == nil {
		return c.JSON(fiber.Map{"data": packs, "zone_id": zoneID, "source": "certificate_packs"})
	}

	// 2. Fallback: Universal SSL settings (disponible en todos los planes)
	universal, uErr := client.GetUniversalSSLSettings(zoneID)
	if uErr != nil {
		// Devolver ambos errores para diagnóstico
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error":           packsErr.Error(),
			"universal_error": uErr.Error(),
			"hint":            "El endpoint ssl/certificate_packs requiere permiso 'SSL and Certificates:Read'. Si usas API Token, asegúrate de que tenga ese permiso.",
		})
	}
	return c.JSON(fiber.Map{
		"data":    nil,
		"zone_id": zoneID,
		"source":  "universal_ssl",
		"universal_ssl": fiber.Map{
			"enabled": universal.Enabled,
			"note":    "Plan actual no expone certificate_packs. Universal SSL: " + func() string { if universal.Enabled { return "ACTIVO" }; return "INACTIVO" }(),
		},
		"packs_error": packsErr.Error(),
	})
}

// GetCloudflareFirewall devuelve las reglas de firewall de una zona (:zone_id).
func GetCloudflareFirewall(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	if zoneID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	data, err := client.GetFirewallRules(zoneID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": data, "zone_id": zoneID})
}

// ─── DNS CRUD ─────────────────────────────────────────────────────────────────

// CreateCloudflareDNS crea un nuevo registro DNS en la zona (:zone_id).
func CreateCloudflareDNS(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	if zoneID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
	}
	var input services.CFDNSRecordInput
	if err := c.BodyParser(&input); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	if input.Type == "" || input.Name == "" || input.Content == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type, name y content son requeridos"})
	}
	if input.TTL == 0 {
		input.TTL = 1 // automático
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	record, err := client.CreateDNSRecord(zoneID, input)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(fiber.StatusCreated).JSON(fiber.Map{"data": record})
}

// UpdateCloudflareDNS actualiza (PUT completo) un registro DNS (:zone_id/:record_id).
func UpdateCloudflareDNS(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	recordID := c.Params("record_id")
	if zoneID == "" || recordID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id y record_id requeridos"})
	}
	var input services.CFDNSRecordInput
	if err := c.BodyParser(&input); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	if input.Type == "" || input.Name == "" || input.Content == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type, name y content son requeridos"})
	}
	if input.TTL == 0 {
		input.TTL = 1
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	record, err := client.UpdateDNSRecord(zoneID, recordID, input)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": record})
}

// PatchCloudflareDNS actualiza parcialmente un registro DNS (:zone_id/:record_id).
func PatchCloudflareDNS(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	recordID := c.Params("record_id")
	if zoneID == "" || recordID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id y record_id requeridos"})
	}
	var input services.CFDNSRecordInput
	if err := c.BodyParser(&input); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	record, err := client.PatchDNSRecord(zoneID, recordID, input)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": record})
}

// DeleteCloudflareDNS elimina un registro DNS (:zone_id/:record_id).
func DeleteCloudflareDNS(c *fiber.Ctx) error {
	zoneID := c.Params("zone_id")
	recordID := c.Params("record_id")
	if zoneID == "" || recordID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id y record_id requeridos"})
	}
	client, err := cloudflareClient()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No se encontró configuración activa de Cloudflare",
		})
	}
	if err := client.DeleteDNSRecord(zoneID, recordID); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Registro DNS eliminado"})
}
