package controllers

import (
	"crypto/rand"
	"encoding/hex"
	"math"
	"strconv"
	"strings"

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

var pasarelasValidas = map[string]bool{"bold": true, "dlocal": true, "paypal": true}

// PagosExternosIndex renderiza el panel de administración de servicios de pago externos.
func PagosExternosIndex(c *fiber.Ctx) error {
	return c.Render("pagos_externos", fiber.Map{
		"user":    c.Locals("user"),
		"modules": c.Locals("userModules"),
	}, "layouts/main")
}

// GetServiciosPagoExterno devuelve la lista paginada. El token nunca se
// devuelve completo, solo el preview guardado al crearlo/regenerarlo.
func GetServiciosPagoExterno(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	limit := 20
	offset := (page - 1) * limit

	items, total, err := models.GetAllServiciosPagoExterno(limit, offset)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{
		"items":      items,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
	})
}

type servicioPagoReq struct {
	Nombre               string   `json:"nombre"`
	IPsPermitidas        []string `json:"ips_permitidas"`
	PasarelasHabilitadas []string `json:"pasarelas_habilitadas"`
	NotificarWebhook     bool     `json:"notificar_webhook"`
	CallbackURL          string   `json:"callback_url"`
	NotificarTelegram    bool     `json:"notificar_telegram"`
	TelegramChatID       string   `json:"telegram_chat_id"`
	Activo               bool     `json:"activo"`
}

func (r servicioPagoReq) validar() (pasarelas []string, ips []string, err error) {
	if strings.TrimSpace(r.Nombre) == "" {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "nombre es requerido")
	}
	for _, p := range r.PasarelasHabilitadas {
		p = strings.ToLower(strings.TrimSpace(p))
		if p == "" {
			continue
		}
		if !pasarelasValidas[p] {
			return nil, nil, fiber.NewError(fiber.StatusBadRequest, "pasarela inválida: "+p)
		}
		pasarelas = append(pasarelas, p)
	}
	if len(pasarelas) == 0 {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "selecciona al menos una pasarela habilitada")
	}
	for _, ip := range r.IPsPermitidas {
		ip = strings.TrimSpace(ip)
		if ip != "" {
			ips = append(ips, ip)
		}
	}
	if r.NotificarWebhook && strings.TrimSpace(r.CallbackURL) == "" {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "callback_url es requerido si el webhook de notificación está activado")
	}
	if r.NotificarTelegram && strings.TrimSpace(r.TelegramChatID) == "" {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "telegram_chat_id es requerido si la notificación por Telegram está activada")
	}
	return pasarelas, ips, nil
}

func generarSecretoHex(nbytes int) (string, error) {
	b := make([]byte, nbytes)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

// CreateServicioPagoExternoHandler crea un nuevo servicio y devuelve el token
// en texto plano — es la única respuesta donde vendrá completo.
func CreateServicioPagoExternoHandler(c *fiber.Ctx) error {
	var req servicioPagoReq
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	pasarelas, ips, err := req.validar()
	if err != nil {
		return c.Status(err.(*fiber.Error).Code).JSON(fiber.Map{"error": err.Error()})
	}

	item := &models.ServicioPagoExterno{
		Nombre:               strings.TrimSpace(req.Nombre),
		IPsPermitidas:        strings.Join(ips, ","),
		PasarelasHabilitadas: models.JoinModulos(pasarelas),
		NotificarWebhook:     req.NotificarWebhook,
		CallbackURL:          strings.TrimSpace(req.CallbackURL),
		NotificarTelegram:    req.NotificarTelegram,
		TelegramChatID:       strings.TrimSpace(req.TelegramChatID),
		Activo:               true,
		CreadoPorID:          extraerUserID(c),
	}
	if req.NotificarWebhook {
		secret, err := generarSecretoHex(32)
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo generar el secreto de firma"})
		}
		item.CallbackSecret = secret
	}

	tokenPlano, err := models.CreateServicioPagoExterno(item)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"ok":              true,
		"id":              item.ID,
		"token":           tokenPlano,
		"callback_secret": item.CallbackSecret,
		"aviso":           "Guarda el token y el callback_secret ahora: no se volverán a mostrar completos.",
	})
}

// UpdateServicioPagoExternoHandler actualiza un servicio existente (no toca el token).
func UpdateServicioPagoExternoHandler(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}
	var req servicioPagoReq
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	pasarelas, ips, verr := req.validar()
	if verr != nil {
		return c.Status(verr.(*fiber.Error).Code).JSON(fiber.Map{"error": verr.Error()})
	}

	existente, err := models.GetServicioPagoExternoByID(uint(id))
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "servicio no encontrado"})
	}

	updates := map[string]interface{}{
		"nombre":                strings.TrimSpace(req.Nombre),
		"ips_permitidas":        strings.Join(ips, ","),
		"pasarelas_habilitadas": models.JoinModulos(pasarelas),
		"notificar_webhook":     req.NotificarWebhook,
		"callback_url":          strings.TrimSpace(req.CallbackURL),
		"notificar_telegram":    req.NotificarTelegram,
		"telegram_chat_id":      strings.TrimSpace(req.TelegramChatID),
		"activo":                req.Activo,
	}
	// Si se activa el webhook y todavía no tenía secreto (se creó sin él), generarlo ahora.
	if req.NotificarWebhook && existente.CallbackSecret == "" {
		secret, err := generarSecretoHex(32)
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo generar el secreto de firma"})
		}
		updates["callback_secret"] = secret
	}

	if err := models.UpdateServicioPagoExterno(uint(id), updates); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// RegenerarTokenServicioPagoHandler invalida el token actual y devuelve uno nuevo.
func RegenerarTokenServicioPagoHandler(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}
	tokenPlano, err := models.RegenerarTokenServicioPago(uint(id))
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true, "token": tokenPlano, "aviso": "El token anterior dejó de funcionar. Guarda este ahora, no se volverá a mostrar."})
}

func DeleteServicioPagoExternoHandler(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}
	if err := models.DeleteServicioPagoExterno(uint(id)); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// GetSolicitudesPagoExternoHandler devuelve el historial de cobros, opcionalmente
// filtrado por servicio, para trazabilidad/soporte.
func GetSolicitudesPagoExternoHandler(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	limit := 20
	offset := (page - 1) * limit
	servicioID, _ := strconv.ParseUint(c.Query("servicio_id", "0"), 10, 64)

	items, total, err := models.GetAllSolicitudesPagoExterna(limit, offset, uint(servicioID))
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{
		"items":      items,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
	})
}
