package controllers

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

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

// ─── Panel: lista de configuraciones ─────────────────────────────────────────

// SaasApiIndex renderiza la vista del panel de integraciones SaaS.
func SaasApiIndex(c *fiber.Ctx) error {
	data := fiber.Map{
		"user":    c.Locals("user").(map[string]interface{}),
		"modules": c.Locals("userModules"),
	}
	if err := c.Render("saas_api", data, "layouts/main"); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return nil
}

// GetSaasApiConfigs devuelve la lista paginada en JSON.
func GetSaasApiConfigs(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.GetAllSaasApiConfigs(limit, offset)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	saasOpts, _ := models.GetAllSaasProductosSelect()

	return c.JSON(fiber.Map{
		"items":      items,
		"saas":       saasOpts,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
		"limit":      limit,
	})
}

// CreateSaasApiConfig crea una nueva configuración de integración.
func CreateSaasApiConfig(c *fiber.Ctx) error {
	type Req struct {
		SaasID          uint   `json:"saas_id"`
		Nombre          string `json:"nombre"`
		Pasarela        string `json:"pasarela"`
		EndpointURL     string `json:"endpoint_url"`
		Metodo          string `json:"metodo"`
		ApiKeyHeader    string `json:"api_key_header"`
		ApiKeyValue     string `json:"api_key_value"`
		PayloadTemplate string `json:"payload_template"`
		TimeoutSeg      int    `json:"timeout_seg"`
		Activo          bool   `json:"activo"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	if req.SaasID == 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "saas_id es requerido"})
	}
	if strings.TrimSpace(req.Nombre) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "nombre es requerido"})
	}
	if strings.TrimSpace(req.EndpointURL) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "endpoint_url es requerido"})
	}
	metodo := strings.ToUpper(strings.TrimSpace(req.Metodo))
	if metodo == "" {
		metodo = "POST"
	}
	pasarela := strings.ToLower(strings.TrimSpace(req.Pasarela))
	if pasarela == "" {
		pasarela = "ambas"
	}
	timeout := req.TimeoutSeg
	if timeout <= 0 {
		timeout = 10
	}

	item := models.SaasApiConfig{
		SaasID:          req.SaasID,
		Nombre:          req.Nombre,
		Pasarela:        pasarela,
		EndpointURL:     req.EndpointURL,
		Metodo:          metodo,
		ApiKeyHeader:    req.ApiKeyHeader,
		ApiKeyValue:     req.ApiKeyValue,
		PayloadTemplate: req.PayloadTemplate,
		TimeoutSeg:      timeout,
		Activo:          req.Activo,
		WebhookToken:    helpers.RandomString(32),
	}
	if err := models.CreateSaasApiConfig(&item); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(fiber.StatusCreated).JSON(item)
}

// UpdateSaasApiConfig actualiza una configuración existente.
func UpdateSaasApiConfig(c *fiber.Ctx) error {
	idParam := c.Params("id")
	id64, err := strconv.ParseUint(idParam, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}

	item, err := models.GetSaasApiConfigByID(uint(id64))
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "no encontrado"})
	}

	type Req struct {
		SaasID          uint   `json:"saas_id"`
		Nombre          string `json:"nombre"`
		Pasarela        string `json:"pasarela"`
		EndpointURL     string `json:"endpoint_url"`
		Metodo          string `json:"metodo"`
		ApiKeyHeader    string `json:"api_key_header"`
		ApiKeyValue     string `json:"api_key_value"`
		PayloadTemplate string `json:"payload_template"`
		TimeoutSeg      int    `json:"timeout_seg"`
		Activo          bool   `json:"activo"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}

	if req.SaasID != 0 {
		item.SaasID = req.SaasID
	}
	if strings.TrimSpace(req.Nombre) != "" {
		item.Nombre = req.Nombre
	}
	pasarela := strings.ToLower(strings.TrimSpace(req.Pasarela))
	if pasarela != "" {
		item.Pasarela = pasarela
	}
	if strings.TrimSpace(req.EndpointURL) != "" {
		item.EndpointURL = req.EndpointURL
	}
	metodo := strings.ToUpper(strings.TrimSpace(req.Metodo))
	if metodo != "" {
		item.Metodo = metodo
	}
	item.ApiKeyHeader = req.ApiKeyHeader
	// Solo actualizar api_key_value si se envió un valor (para no borrar el secreto con "")
	if req.ApiKeyValue != "" {
		item.ApiKeyValue = req.ApiKeyValue
	}
	item.PayloadTemplate = req.PayloadTemplate
	if req.TimeoutSeg > 0 {
		item.TimeoutSeg = req.TimeoutSeg
	}
	item.Activo = req.Activo

	if err := models.UpdateSaasApiConfig(item); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(item)
}

// DeleteSaasApiConfig elimina una configuración.
func DeleteSaasApiConfig(c *fiber.Ctx) error {
	idParam := c.Params("id")
	id64, err := strconv.ParseUint(idParam, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}
	if err := models.DeleteSaasApiConfig(uint(id64)); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ProbaSaasApiConfig envía una petición de prueba al endpoint configurado.
// Body JSON opcional: { "payload": "..." }  — si no se envía usa el payload_template tal cual.
func ProbaSaasApiConfig(c *fiber.Ctx) error {
	idParam := c.Params("id")
	id64, err := strconv.ParseUint(idParam, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}
	cfg, err := models.GetSaasApiConfigByID(uint(id64))
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "no encontrado"})
	}

	type Req struct {
		Payload string `json:"payload"`
	}
	var req Req
	_ = c.BodyParser(&req)

	body := strings.TrimSpace(req.Payload)
	if body == "" {
		body = strings.TrimSpace(cfg.PayloadTemplate)
	}

	timeout := cfg.TimeoutSeg
	if timeout <= 0 {
		timeout = 10
	}
	client := &http.Client{Timeout: time.Duration(timeout) * time.Second}

	var httpReq *http.Request
	metodo := strings.ToUpper(cfg.Metodo)
	if metodo == "" {
		metodo = "POST"
	}

	if body != "" && metodo != "GET" {
		httpReq, err = http.NewRequest(metodo, cfg.EndpointURL, strings.NewReader(body))
		if err != nil {
			return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{"error": "URL inválida: " + err.Error()})
		}
		httpReq.Header.Set("Content-Type", "application/json")
	} else {
		httpReq, err = http.NewRequest(metodo, cfg.EndpointURL, nil)
		if err != nil {
			return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{"error": "URL inválida: " + err.Error()})
		}
	}

	if cfg.ApiKeyHeader != "" && cfg.ApiKeyValue != "" {
		httpReq.Header.Set(cfg.ApiKeyHeader, cfg.ApiKeyValue)
	}
	httpReq.Header.Set("User-Agent", "u-site-tester/1.0")

	start := time.Now()
	resp, err := client.Do(httpReq)
	latency := time.Since(start).Milliseconds()

	if err != nil {
		return c.JSON(fiber.Map{
			"ok":         false,
			"error":      err.Error(),
			"latency_ms": latency,
		})
	}
	defer resp.Body.Close()

	respBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) // máx 64 KB
	return c.JSON(fiber.Map{
		"ok":          resp.StatusCode >= 200 && resp.StatusCode < 300,
		"http_status": resp.StatusCode,
		"body":        string(respBytes),
		"latency_ms":  latency,
	})
}

// ─── Panel: logs de despacho ──────────────────────────────────────────────────

// SaasDispatchLogIndex renderiza la vista de logs de despacho.
func SaasDispatchLogIndex(c *fiber.Ctx) error {
	data := fiber.Map{
		"user":    c.Locals("user").(map[string]interface{}),
		"modules": c.Locals("userModules"),
	}
	if err := c.Render("saas_dispatch_logs", data, "layouts/main"); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return nil
}

// GetSaasDispatchLogs devuelve los logs de despacho paginados.
func GetSaasDispatchLogs(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	limit := 30
	offset := (page - 1) * limit

	items, total, err := models.GetSaasDispatchLogs(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,
		"limit":      limit,
	})
}

// ─── Webhook entrante por token ───────────────────────────────────────────────

// SaasWebhookInHandler recibe llamadas entrantes del SaaS externo identificadas
// por el WebhookToken único de la integración. No requiere autenticación.
// Ruta pública: POST /webhooks/saas-in/:token
func SaasWebhookInHandler(c *fiber.Ctx) error {
	token := strings.TrimSpace(c.Params("token"))
	if token == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "token requerido"})
	}

	cfg, err := models.GetSaasApiConfigByWebhookToken(token)
	if err != nil {
		// Responder 200 para evitar reintentos agresivos de sistemas externos
		log.Printf("[SaasWebhookIn] Token desconocido: %s", token)
		return c.Status(fiber.StatusOK).JSON(fiber.Map{"ok": false, "msg": "token no reconocido"})
	}

	// Capturar headers relevantes como JSON
	headersMap := map[string]string{}
	c.Request().Header.VisitAll(func(k, v []byte) {
		key := string(k)
		// Solo guardar headers informativos, excluir cookies/auth
		if key != "Cookie" && key != "Authorization" {
			headersMap[key] = string(v)
		}
	})
	headersJSON, _ := json.Marshal(headersMap)

	rawBody := string(c.Body())

	entry := models.SaasWebhookInLog{
		SaasApiConfigID: cfg.ID,
		Token:           token,
		Metodo:          c.Method(),
		IP:              c.IP(),
		Body:            rawBody,
		Headers:         string(headersJSON),
	}
	_ = models.SaveSaasWebhookInLog(&entry)

	log.Printf("[SaasWebhookIn] Integración '%s' (ID=%d) recibió callback desde IP=%s method=%s",
		cfg.Nombre, cfg.ID, c.IP(), c.Method())

	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"ok":           true,
		"integracion":  cfg.Nombre,
		"saas_api_id":  cfg.ID,
		"message":      fmt.Sprintf("Webhook recibido para integración: %s", cfg.Nombre),
	})
}
