package controllers

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

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

// filterQueryParam elimina un parámetro específico del querystring.
func filterQueryParam(qs, param string) string {
	vals, err := url.ParseQuery(qs)
	if err != nil {
		return qs
	}
	vals.Del(param)
	return vals.Encode()
}

// ─── helpers internos ────────────────────────────────────────────────────────

// coolifyDo ejecuta una petición a la API de Coolify y devuelve el body crudo.
func coolifyDo(method, endpoint string, reqBody io.Reader, contentType string, cfg *models.CoolifyConfig) ([]byte, int, error) {
	base := strings.TrimRight(cfg.BaseURL, "/")
	url := fmt.Sprintf("%s/api/v1%s", base, endpoint)

	client := &http.Client{Timeout: 30 * time.Second}
	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return nil, 0, err
	}
	req.Header.Set("Authorization", "Bearer "+cfg.ApiToken)
	req.Header.Set("Accept", "application/json")
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, 0, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
	return body, resp.StatusCode, nil
}

// coolifyResolveConfig retorna la config a usar según ?config_id= o la activa por defecto.
func coolifyResolveConfig(c *fiber.Ctx) (*models.CoolifyConfig, error) {
	if idStr := c.Query("config_id"); idStr != "" {
		id, err := strconv.ParseUint(idStr, 10, 32)
		if err != nil {
			return nil, fmt.Errorf("config_id inválido")
		}
		return models.GetCoolifyConfigByID(uint(id))
	}
	return models.GetCoolifyConfig()
}

// coolifyProxy resuelve config, aplica qs de la request y responde al frontend.
func coolifyProxy(c *fiber.Ctx, method, endpoint string) error {
	cfg, err := coolifyResolveConfig(c)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Configura primero la integración Coolify"})
	}
	if !cfg.Activo {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La integración Coolify está inactiva"})
	}

	// Filtrar config_id del querystring para no pasarlo a Coolify
	qs := string(c.Request().URI().QueryString())
	if qs != "" {
		filtered := filterQueryParam(qs, "config_id")
		if filtered != "" {
			qs = filtered
		} else {
			qs = ""
		}
	}
	ep := endpoint
	if qs != "" {
		ep = endpoint + "?" + qs
	}

	var bodyReader io.Reader
	ct := ""
	if raw := c.Body(); len(raw) > 0 {
		bodyReader = strings.NewReader(string(raw))
		ct = c.Get("Content-Type", "application/json")
	}

	body, status, err := coolifyDo(method, ep, bodyReader, ct, cfg)
	if err != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
	}

	var result json.RawMessage
	if err := json.Unmarshal(body, &result); err != nil {
		result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body)))
	}
	c.Status(status)
	return c.JSON(result)
}

// ─── Página principal ─────────────────────────────────────────────────────────

func CoolifyIndex(c *fiber.Ctx) error {
	cfgs, _ := models.GetAllCoolifyConfigs()
	return c.Render("coolify", fiber.Map{
		"user":    c.Locals("user").(map[string]interface{}),
		"modules": c.Locals("userModules"),
		"configs": cfgs,
	}, "layouts/main")
}

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

func CoolifyGetConfig(c *fiber.Ctx) error {
	cfg, err := models.GetCoolifyConfig()
	if err != nil {
		return c.JSON(fiber.Map{"config": nil})
	}
	masked := "••••••••"
	if cfg.ApiToken == "" {
		masked = ""
	}
	return c.JSON(fiber.Map{"config": fiber.Map{
		"ID":        cfg.ID,
		"nombre":    cfg.Nombre,
		"base_url":  cfg.BaseURL,
		"api_token": masked,
		"activo":    cfg.Activo,
	}})
}

func CoolifySaveConfig(c *fiber.Ctx) error {
	type Req struct {
		Nombre   string `json:"nombre"`
		BaseURL  string `json:"base_url"`
		ApiToken string `json:"api_token"`
		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"})
	}
	req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/")
	if req.BaseURL == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "base_url es requerido"})
	}
	cfg, err := models.UpsertCoolifyConfig(req.Nombre, req.BaseURL, req.ApiToken, req.Activo)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true, "config": cfg})
}

// CoolifyTestConnection verifica conectividad con /health (no requiere auth).
func CoolifyTestConnection(c *fiber.Ctx) error {
	cfg, err := models.GetCoolifyConfig()
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Sin config"})
	}
	base := strings.TrimRight(cfg.BaseURL, "/")
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(base + "/api/health")
	if err != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
	var result json.RawMessage
	if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
		result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body)))
	}
	c.Status(resp.StatusCode)
	return c.JSON(result)
}

// ─── Applications ─────────────────────────────────────────────────────────────

func CoolifyListApplications(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications")
}

func CoolifyGetApplication(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid"))
}

func CoolifyApplicationLogs(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/logs")
}

func CoolifyApplicationEnvs(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/envs")
}

func CoolifyApplicationEnvCreate(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodPost, "/applications/"+c.Params("uuid")+"/envs")
}

func CoolifyApplicationEnvUpdateBulk(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodPatch, "/applications/"+c.Params("uuid")+"/envs")
}

func CoolifyApplicationEnvDelete(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodDelete, "/applications/"+c.Params("uuid")+"/envs/"+c.Params("env_id"))
}

func CoolifyApplicationStart(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/start")
}

func CoolifyApplicationStop(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/stop")
}

func CoolifyApplicationRestart(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/restart")
}

func CoolifyApplicationDeployments(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/deployments")
}

func CoolifyApplicationDeploy(c *fiber.Ctx) error {
	// POST /api/v1/deploy?uuid={uuid}&force=false
	return coolifyProxy(c, http.MethodPost, "/deploy")
}

// ─── Servers ──────────────────────────────────────────────────────────────────

func CoolifyListServers(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/servers")
}

func CoolifyGetServer(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid"))
}

func CoolifyServerResources(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/resources")
}

func CoolifyServerDomains(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/domains")
}

func CoolifyServerValidate(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/validate")
}

// ─── Services ─────────────────────────────────────────────────────────────────

func CoolifyListServices(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/services")
}

func CoolifyGetService(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid"))
}

func CoolifyServiceStart(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/start")
}

func CoolifyServiceStop(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/stop")
}

func CoolifyServiceRestart(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/restart")
}

func CoolifyServiceEnvs(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/envs")
}

func CoolifyServiceEnvUpdateBulk(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodPatch, "/services/"+c.Params("uuid")+"/envs")
}

// ─── Databases ────────────────────────────────────────────────────────────────

func CoolifyListDatabases(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/databases")
}

func CoolifyGetDatabase(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid"))
}

func CoolifyDatabaseStart(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/start")
}

func CoolifyDatabaseStop(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/stop")
}

func CoolifyDatabaseRestart(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/restart")
}

// ─── Projects ─────────────────────────────────────────────────────────────────

func CoolifyListProjects(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/projects")
}

func CoolifyGetProject(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/projects/"+c.Params("uuid"))
}

func CoolifyProjectEnvironments(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/projects/"+c.Params("uuid")+"/environments")
}

// ─── Deployments ──────────────────────────────────────────────────────────────

func CoolifyListDeployments(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/deployments")
}

func CoolifyGetDeployment(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/deployments/"+c.Params("uuid"))
}

// ─── Teams ────────────────────────────────────────────────────────────────────

func CoolifyListTeams(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/teams")
}

func CoolifyCurrentTeam(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/teams/current")
}

func CoolifyTeamMembers(c *fiber.Ctx) error {
	return coolifyProxy(c, http.MethodGet, "/teams/current/members")
}

// CoolifyWebhook recibe notificaciones push de Coolify y las reenvía a Telegram.
// Configurar en Coolify → Settings → Notifications → Add Notification → Webhook
// URL: https://admin.u-site.app/webhooks/coolify/:config_id
func CoolifyWebhook(c *fiber.Ctx) error {
	body := c.Body()
	log.Printf("[COOLIFY_WEBHOOK] config_id=%s payload=%s", c.Params("config_id"), string(body))

	if len(body) == 0 {
		return c.SendStatus(fiber.StatusOK)
	}

	// Identificar la instancia Coolify si viene en la ruta
	var instanceName string
	if idStr := c.Params("config_id"); idStr != "" {
		if id, err := strconv.ParseUint(idStr, 10, 32); err == nil {
			if cfg, err := models.GetCoolifyConfigByID(uint(id)); err == nil {
				instanceName = cfg.Nombre
			}
		}
	}

	go sendCoolifyTelegramNotif(body, instanceName)
	return c.SendStatus(fiber.StatusOK)
}

// sendCoolifyTelegramNotif parsea el payload de Coolify y lo envía a Telegram.
func sendCoolifyTelegramNotif(body []byte, instanceName string) {
	// Intentar parsear como JSON
	var payload map[string]interface{}
	isJSON := json.Unmarshal(body, &payload) == nil

	// Extraer campos con múltiples nombres posibles (Coolify cambia formato según versión)
	get := func(keys ...string) string {
		if !isJSON {
			return ""
		}
		for _, k := range keys {
			if v, ok := payload[k]; ok {
				if s, ok := v.(string); ok && s != "" {
					return s
				}
			}
		}
		return ""
	}

	appName := get("application_name", "name", "app_name", "resource_name")
	appUUID := get("application_uuid", "uuid", "resource_uuid")
	status := get("status", "type", "event", "deployment_status")
	message := get("message", "description", "text", "body")
	fqdn := get("fqdn", "url", "application_url", "application_fqdn")
	serverName := get("server_name", "server")

	// Si no pudimos parsear como JSON, tratar el body entero como mensaje
	if !isJSON {
		message = strings.TrimSpace(string(body))
		if len(message) > 500 {
			message = message[:500] + "…"
		}
	}

	// Emoji según estado
	emoji := "🔔"
	statusLower := strings.ToLower(status + " " + message)
	switch {
	case strings.Contains(statusLower, "success") || strings.Contains(statusLower, "exitoso") || strings.Contains(statusLower, "finished"):
		emoji = "✅"
	case strings.Contains(statusLower, "fail") || strings.Contains(statusLower, "error") || strings.Contains(statusLower, "fallo"):
		emoji = "❌"
	case strings.Contains(statusLower, "running") || strings.Contains(statusLower, "deploying") || strings.Contains(statusLower, "start") || strings.Contains(statusLower, "building"):
		emoji = "🚀"
	case strings.Contains(statusLower, "stop") || strings.Contains(statusLower, "detenido"):
		emoji = "⏹"
	case strings.Contains(statusLower, "restart") || strings.Contains(statusLower, "reinici"):
		emoji = "🔄"
	}

	// Construir el mensaje
	lines := []string{fmt.Sprintf("%s <b>Coolify</b>", emoji)}
	if instanceName != "" {
		lines = append(lines, fmt.Sprintf("Instancia: <b>%s</b>", instanceName))
	}
	if appName != "" {
		lines = append(lines, fmt.Sprintf("App: <code>%s</code>", appName))
	}
	if appUUID != "" && appUUID != appName {
		lines = append(lines, fmt.Sprintf("UUID: <code>%s</code>", appUUID))
	}
	if serverName != "" {
		lines = append(lines, fmt.Sprintf("Servidor: %s", serverName))
	}
	if status != "" {
		lines = append(lines, fmt.Sprintf("Estado: <b>%s</b>", status))
	}
	if message != "" && message != status {
		if len(message) > 300 {
			message = message[:300] + "…"
		}
		lines = append(lines, fmt.Sprintf("ℹ️ %s", message))
	}
	if fqdn != "" {
		lines = append(lines, fmt.Sprintf("🌐 %s", fqdn))
	}

	text := strings.Join(lines, "\n")

	// Obtener el bot del agente (TelegramConfig ID que tenga agent bot activo)
	tgCfg, err := models.GetAgentTelegramConfig()
	if err != nil {
		log.Printf("[COOLIFY_WEBHOOK] Sin config Telegram para notificar: %v", err)
		return
	}

	// Enviar a todos los chats autorizados del agente
	auths, _ := models.GetAllAgentAuth()
	if len(auths) == 0 {
		log.Printf("[COOLIFY_WEBHOOK] Sin chats autorizados para notificar")
		return
	}

	svc := &services.TelegramService{BotToken: tgCfg.BotToken}
	for _, auth := range auths {
		if !auth.Activo {
			continue
		}
		if err := svc.SendMessage(auth.ChatID, text); err != nil {
			log.Printf("[COOLIFY_WEBHOOK] Error enviando a chat %d: %v", auth.ChatID, err)
		}
	}
}

// ─── CRUD de instancias Coolify ───────────────────────────────────────────────

func CoolifyListConfigs(c *fiber.Ctx) error {
	items, err := models.GetAllCoolifyConfigs()
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Ocultar tokens
	type safe struct {
		ID      uint   `json:"id"`
		Nombre  string `json:"nombre"`
		BaseURL string `json:"base_url"`
		Activo  bool   `json:"activo"`
	}
	out := make([]safe, len(items))
	for i, cfg := range items {
		out[i] = safe{ID: cfg.ID, Nombre: cfg.Nombre, BaseURL: cfg.BaseURL, Activo: cfg.Activo}
	}
	return c.JSON(fiber.Map{"items": out})
}

func CoolifyGetConfigByID(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "id inválido"})
	}
	cfg, err := models.GetCoolifyConfigByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "instancia no encontrada"})
	}
	return c.JSON(fiber.Map{
		"id":       cfg.ID,
		"nombre":   cfg.Nombre,
		"base_url": cfg.BaseURL,
		"activo":   cfg.Activo,
	})
}

func CoolifyCreateConfig(c *fiber.Ctx) error {
	type Req struct {
		Nombre   string `json:"nombre"`
		BaseURL  string `json:"base_url"`
		ApiToken string `json:"api_token"`
		Activo   bool   `json:"activo"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
	}
	req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/")
	if req.BaseURL == "" || req.ApiToken == "" {
		return c.Status(400).JSON(fiber.Map{"error": "base_url y api_token son requeridos"})
	}
	cfg := &models.CoolifyConfig{Nombre: req.Nombre, BaseURL: req.BaseURL, ApiToken: req.ApiToken, Activo: req.Activo}
	if err := models.CreateCoolifyConfig(cfg); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(fiber.Map{"ok": true, "id": cfg.ID})
}

func CoolifyUpdateConfig(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "id inválido"})
	}
	type Req struct {
		Nombre   string `json:"nombre"`
		BaseURL  string `json:"base_url"`
		ApiToken string `json:"api_token"`
		Activo   bool   `json:"activo"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
	}
	req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/")
	if req.BaseURL == "" {
		return c.Status(400).JSON(fiber.Map{"error": "base_url es requerido"})
	}
	cfg, err := models.UpdateCoolifyConfig(uint(id), req.Nombre, req.BaseURL, req.ApiToken, req.Activo)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true, "id": cfg.ID})
}

func CoolifyDeleteConfig(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "id inválido"})
	}
	if err := models.DeleteCoolifyConfig(uint(id)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func CoolifyTestConfig(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "id inválido"})
	}
	cfg, err := models.GetCoolifyConfigByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "config no encontrada"})
	}
	base := strings.TrimRight(cfg.BaseURL, "/")
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(base + "/api/health")
	if err != nil {
		return c.Status(502).JSON(fiber.Map{"error": err.Error()})
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
	var result json.RawMessage
	if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
		result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body)))
	}
	c.Status(resp.StatusCode)
	return c.JSON(result)
}
