package controllers

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"time"

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

// AgentHeartbeat recibe métricas del agente instalado en un servidor.
// Ruta pública: POST /agent/heartbeat — autenticada por token en JSON body.
func AgentHeartbeat(c *fiber.Ctx) error {
	type HeartbeatRequest struct {
		Token    string `json:"token"`
		Metricas string `json:"metricas"` // JSON string ya serializado por el agente
	}

	var req HeartbeatRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ok": false, "error": "body inválido"})
	}
	if req.Token == "" {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ok": false, "error": "token requerido"})
	}

	db := app.Http.Database.DB
	var servidor models.Servidor
	if err := db.Where("agent_token = ?", req.Token).First(&servidor).Error; err != nil {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ok": false, "error": "token inválido"})
	}

	now := time.Now()
	updates := map[string]interface{}{
		"agent_last_seen": &now,
		"ultimo_ping":     now.Format(time.RFC3339),
	}
	if req.Metricas != "" {
		updates["metricas_json"] = req.Metricas
		// Actualizar campos planos desde las métricas del agente (la verdad viene del agente)
		type agentRAM struct {
			TotalGB float64 `json:"total_gb"`
		}
		type agentCPU struct {
			Nucleos int `json:"nucleos"`
		}
		type agentDisco struct {
			TotalGB float64 `json:"total_gb"`
		}
		type agentPayload struct {
			OS    string     `json:"os"`
			RAM   agentRAM   `json:"ram"`
			CPU   agentCPU   `json:"cpu"`
			Disco agentDisco `json:"disco"`
		}
		var m agentPayload
		if jsonErr := json.Unmarshal([]byte(req.Metricas), &m); jsonErr == nil {
			if m.OS != "" {
				updates["so"] = m.OS
			}
			if m.RAM.TotalGB > 0 {
				updates["ram"] = fmt.Sprintf("%.0f GB", m.RAM.TotalGB)
			}
			if m.CPU.Nucleos > 0 {
				updates["nucleos"] = fmt.Sprintf("%d", m.CPU.Nucleos)
			}
			if m.Disco.TotalGB > 0 {
				updates["disco"] = fmt.Sprintf("%.0f GB", m.Disco.TotalGB)
			}
		}
	}

	if err := db.Model(&servidor).Updates(updates).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"ok": false, "error": "no se pudo guardar"})
	}

	return c.JSON(fiber.Map{"ok": true, "servidor_id": servidor.ID})
}

// GenerateAgentToken genera o regenera el token de agente para un servidor.
// Ruta protegida: POST /app/servidor/:id/agent-token
func GenerateAgentToken(c *fiber.Ctx) error {
	id := c.Params("id")
	db := app.Http.Database.DB

	var servidor models.Servidor
	if err := db.First(&servidor, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "servidor no encontrado"})
	}

	// Generar token aleatorio de 32 bytes (64 hex chars)
	tokenBytes := make([]byte, 32)
	if _, err := rand.Read(tokenBytes); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo generar token"})
	}
	token := hex.EncodeToString(tokenBytes)

	if err := db.Model(&servidor).Update("agent_token", token).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo guardar token"})
	}

	return c.JSON(fiber.Map{
		"ok":          true,
		"agent_token": token,
		"servidor_id": servidor.ID,
	})
}
