package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"log"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/bigtux/ophion/internal/auth"
	"github.com/bigtux/ophion/internal/otel"
	"github.com/bigtux/ophion/internal/security"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/gofiber/fiber/v2/middleware/recover"
	"github.com/google/uuid"
	_ "github.com/lib/pq"
)

// ═══════════════════════════════════════════════════════════
// 🐍 OPHION Server - Observability Platform API
// ═══════════════════════════════════════════════════════════

type Server struct {
	app         *fiber.App
	db          *sql.DB
	authHandler *auth.AuthHandler
}

type Metric struct {
	Timestamp  time.Time         `json:"timestamp"`
	Service    string            `json:"service"`
	Host       string            `json:"host"`
	Name       string            `json:"name"`
	Value      float64           `json:"value"`
	MetricType string            `json:"metric_type"`
	Tags       map[string]string `json:"tags,omitempty"`
}

type LogEntry struct {
	Timestamp   time.Time `json:"timestamp"`
	Service     string    `json:"service"`
	Host        string    `json:"host"`
	Level       string    `json:"level"`
	Message     string    `json:"message"`
	TraceID     string    `json:"trace_id,omitempty"`
	SpanID      string    `json:"span_id,omitempty"`
	Source      string    `json:"source"`
	ContainerID string    `json:"container_id,omitempty"`
}

type Span struct {
	TraceID      string         `json:"trace_id"`
	SpanID       string         `json:"span_id"`
	ParentSpanID string         `json:"parent_span_id,omitempty"`
	Service      string         `json:"service"`
	Operation    string         `json:"operation"`
	StartTime    time.Time      `json:"start_time"`
	EndTime      time.Time      `json:"end_time"`
	DurationNs   int64          `json:"duration_ns"`
	StatusCode   string         `json:"status_code"`
	StatusMsg    string         `json:"status_message,omitempty"`
	Kind         string         `json:"kind"`
	Attributes   map[string]any `json:"attributes,omitempty"`
}

type Alert struct {
	ID         string    `json:"id"`
	Name       string    `json:"name"`
	Severity   string    `json:"severity"`
	Service    string    `json:"service"`
	Host       string    `json:"host"`
	Message    string    `json:"message"`
	Status     string    `json:"status"`
	FiredAt    time.Time `json:"fired_at"`
	ResolvedAt *time.Time `json:"resolved_at,omitempty"`
}

type Agent struct {
	ID        string    `json:"id"`
	Hostname  string    `json:"hostname"`
	IP        string    `json:"ip"`
	Version   string    `json:"version"`
	Status    string    `json:"status"`
	LastSeen  time.Time `json:"last_seen"`
	CreatedAt time.Time `json:"created_at"`
}

func main() {
	// Initialize database
	pgDSN := getEnv("DATABASE_URL", "postgres://ophion:ophion@localhost:5432/ophion?sslmode=disable")
	db, err := sql.Open("postgres", pgDSN)
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}
	defer db.Close()

	// Test connection
	if err := db.Ping(); err != nil {
		log.Printf("⚠ Database not available: %v", err)
	} else {
		log.Println("✓ Connected to PostgreSQL")
		initSchema(db)
		// Create default admin user
		if err := auth.CreateDefaultAdmin(db); err != nil {
			log.Printf("⚠ Failed to create default admin: %v", err)
		}
	}

	// Initialize auth handler
	jwtSecret := getEnv("JWT_SECRET", "ophion-super-secret-key-change-in-production")
	authHandler := auth.NewAuthHandler(db, jwtSecret)

	server := &Server{db: db, authHandler: authHandler}

	// Create Fiber app
	app := fiber.New(fiber.Config{
		AppName:      "OPHION Observability Platform",
		BodyLimit:    50 * 1024 * 1024,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
	})
	server.app = app

	// Middleware
	app.Use(recover.New())
	app.Use(logger.New(logger.Config{
		Format: "${time} ${status} ${method} ${path} ${latency}\n",
	}))
	app.Use(cors.New(cors.Config{
		AllowOrigins: "*",
		AllowHeaders: "Origin, Content-Type, Accept, Authorization",
	}))

	// Routes
	server.setupRoutes()

	// Graceful shutdown
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		sigCh := make(chan os.Signal, 1)
		signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
		<-sigCh
		log.Println("🛑 Shutting down server...")
		cancel()
		app.Shutdown()
	}()

	// Background jobs
	go server.runBackgroundJobs(ctx)

	port := getEnv("PORT", "8080")
	log.Printf("🐍 OPHION server starting on port %s", port)
	if err := app.Listen(":" + port); err != nil {
		log.Fatal(err)
	}
}

func initSchema(db *sql.DB) {
	schema := `
	CREATE TABLE IF NOT EXISTS metrics (
		id SERIAL PRIMARY KEY,
		timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
		service VARCHAR(255) NOT NULL,
		host VARCHAR(255) NOT NULL,
		name VARCHAR(255) NOT NULL,
		value DOUBLE PRECISION NOT NULL,
		metric_type VARCHAR(50),
		tags JSONB
	);

	CREATE TABLE IF NOT EXISTS logs (
		id SERIAL PRIMARY KEY,
		timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
		service VARCHAR(255) NOT NULL,
		host VARCHAR(255) NOT NULL,
		level VARCHAR(20),
		message TEXT,
		trace_id VARCHAR(64),
		span_id VARCHAR(32),
		source VARCHAR(50),
		container_id VARCHAR(64)
	);

	CREATE TABLE IF NOT EXISTS spans (
		id SERIAL PRIMARY KEY,
		trace_id VARCHAR(64) NOT NULL,
		span_id VARCHAR(32) NOT NULL,
		parent_span_id VARCHAR(32),
		service VARCHAR(255) NOT NULL,
		operation VARCHAR(255) NOT NULL,
		start_time TIMESTAMPTZ NOT NULL,
		end_time TIMESTAMPTZ NOT NULL,
		duration_ns BIGINT,
		status_code VARCHAR(20),
		status_message TEXT,
		kind VARCHAR(20),
		attributes JSONB
	);

	CREATE TABLE IF NOT EXISTS agents (
		id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
		hostname VARCHAR(255) NOT NULL UNIQUE,
		ip VARCHAR(45),
		version VARCHAR(50),
		status VARCHAR(20) DEFAULT 'active',
		last_seen TIMESTAMPTZ DEFAULT NOW(),
		created_at TIMESTAMPTZ DEFAULT NOW()
	);

	CREATE TABLE IF NOT EXISTS alerts (
		id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
		name VARCHAR(255) NOT NULL,
		severity VARCHAR(20),
		service VARCHAR(255),
		host VARCHAR(255),
		message TEXT,
		status VARCHAR(20) DEFAULT 'firing',
		fired_at TIMESTAMPTZ DEFAULT NOW(),
		resolved_at TIMESTAMPTZ
	);

	-- Users table for JWT auth
	CREATE TABLE IF NOT EXISTS users (
		id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
		email VARCHAR(255) NOT NULL UNIQUE,
		password_hash VARCHAR(255) NOT NULL,
		role VARCHAR(50) NOT NULL DEFAULT 'user',
		created_at TIMESTAMPTZ DEFAULT NOW()
	);

	-- API Keys table for agent auth
	CREATE TABLE IF NOT EXISTS api_keys (
		id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
		key_hash VARCHAR(64) NOT NULL UNIQUE,
		prefix VARCHAR(20) NOT NULL,
		name VARCHAR(255) NOT NULL,
		user_id UUID REFERENCES users(id) ON DELETE CASCADE,
		created_at TIMESTAMPTZ DEFAULT NOW(),
		last_used TIMESTAMPTZ
	);

	CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON metrics(timestamp DESC);
	CREATE INDEX IF NOT EXISTS idx_metrics_service_name ON metrics(service, name);
	CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp DESC);
	CREATE INDEX IF NOT EXISTS idx_logs_service ON logs(service);
	CREATE INDEX IF NOT EXISTS idx_spans_trace_id ON spans(trace_id);
	CREATE INDEX IF NOT EXISTS idx_spans_service ON spans(service);
	CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
	CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
	`

	if _, err := db.Exec(schema); err != nil {
		log.Printf("Error creating schema: %v", err)
	} else {
		log.Println("✓ Database schema initialized")
	}
}

func (s *Server) setupRoutes() {
	// Health check (public)
	s.app.Get("/health", s.healthCheck)

	// Security headers
	s.app.Use(security.SecurityHeaders())

	// ═══════════════════════════════════════════════════════════
	// 🔭 OTLP HTTP Receiver (OpenTelemetry Protocol)
	// ═══════════════════════════════════════════════════════════
	// Standard OTLP endpoint - can be public or protected based on config
	otlpReceiver := otel.NewOTLPReceiver(s.db)
	
	// OTLP routes (public by default for easy integration)
	// For production, consider adding auth middleware
	s.app.Post("/v1/traces", otlpReceiver.HandleTraces)
	
	// Also support the full path that some SDKs use
	s.app.Post("/v1/traces/", otlpReceiver.HandleTraces)

	// API v1
	api := s.app.Group("/api/v1")

	// ═══════════════════════════════════════════════════════════
	// 🔓 PUBLIC ROUTES (no auth required)
	// ═══════════════════════════════════════════════════════════

	// Auth endpoints
	authGroup := api.Group("/auth")
	authGroup.Post("/login", s.authHandler.Login)
	authGroup.Post("/register", s.authHandler.Register)

	// ═══════════════════════════════════════════════════════════
	// 🔐 PROTECTED ROUTES (auth required)
	// ═══════════════════════════════════════════════════════════

	// Ingest endpoints (for agents - API key or JWT)
	ingest := api.Group("/ingest", s.authMiddleware())
	ingest.Post("/metrics", s.ingestMetrics)
	ingest.Post("/logs", s.ingestLogs)
	ingest.Post("/traces", s.ingestTraces)

	// Legacy ingest routes (agent key auth for simplicity)
	api.Post("/metrics", s.agentAuthMiddleware(), s.ingestMetrics)
	api.Post("/logs", s.agentAuthMiddleware(), s.ingestLogs)
	api.Post("/traces", s.agentAuthMiddleware(), s.ingestTraces)

	// Protected routes
	protected := api.Group("", s.authMiddleware())

	// Query endpoints (for dashboard)
	protected.Get("/metrics", s.queryMetrics)
	protected.Get("/metrics/names", s.getMetricNames)
	protected.Get("/logs", s.queryLogs)
	protected.Get("/traces", s.queryTraces)
	protected.Get("/traces/:traceId", s.getTrace)
	protected.Get("/services", s.getServices)

	// Agents
	protected.Get("/agents", s.getAgents)
	protected.Post("/agents/register", s.registerAgent)

	// Alerts
	protected.Get("/alerts", s.getAlerts)
	protected.Post("/alerts", s.createAlert)
	protected.Put("/alerts/:id/resolve", s.resolveAlert)

	// Dashboard
	protected.Get("/dashboard/overview", s.getDashboardOverview)

	// User info
	protected.Get("/me", s.authHandler.Me)

	// API Keys management (JWT only)
	protected.Post("/api-keys", s.authHandler.CreateAPIKey)
	protected.Get("/api-keys", s.authHandler.ListAPIKeys)
	protected.Delete("/api-keys/:id", s.authHandler.DeleteAPIKey)
}

// authMiddleware creates authentication middleware that accepts both JWT and API keys
func (s *Server) authMiddleware() fiber.Handler {
	return func(c *fiber.Ctx) error {
		authHeader := c.Get("Authorization")
		if authHeader == "" {
			return c.Status(401).JSON(fiber.Map{
				"error":   "Unauthorized",
				"message": "Missing Authorization header",
			})
		}

		token := strings.TrimPrefix(authHeader, "Bearer ")

		// Check if it's an API key (starts with ophion_)
		if strings.HasPrefix(token, security.APIKeyPrefix) {
			return s.authenticateAPIKey(c, token)
		}

		// Otherwise treat as JWT
		return s.authenticateJWT(c, token)
	}
}

// authenticateAPIKey validates API key authentication
func (s *Server) authenticateAPIKey(c *fiber.Ctx, apiKey string) error {
	if !security.ValidateAPIKeyFormat(apiKey) {
		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": "Invalid API key format",
		})
	}

	keyHash := security.HashAPIKey(apiKey)

	var keyID, name, userID string
	err := s.db.QueryRow(`
		SELECT id, name, user_id FROM api_keys WHERE key_hash = $1
	`, keyHash).Scan(&keyID, &name, &userID)

	if err != nil {
		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": "Invalid API key",
		})
	}

	// Update last_used (async)
	go s.db.Exec(`UPDATE api_keys SET last_used = NOW() WHERE id = $1`, keyID)

	// Set context
	c.Locals("auth_type", "api_key")
	c.Locals("api_key_id", keyID)
	c.Locals("user_id", userID)

	return c.Next()
}

// authenticateJWT validates JWT token authentication
func (s *Server) authenticateJWT(c *fiber.Ctx, token string) error {
	authService := s.authHandler.GetAuthService()
	claims, err := authService.ValidateAccessToken(token)
	if err != nil {
		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": err.Error(),
		})
	}

	// Set context
	c.Locals("auth_type", "jwt")
	c.Locals("user_id", claims.UserID)
	c.Locals("email", claims.Email)
	c.Locals("role", claims.Role)
	c.Locals("token_id", claims.TokenID)

	return c.Next()
}

func (s *Server) healthCheck(c *fiber.Ctx) error {
	return c.JSON(fiber.Map{
		"status":    "healthy",
		"service":   "ophion",
		"version":   "0.2.0",
		"timestamp": time.Now(),
	})
}

// ─────────────────────────────────────────────────────────────
// Ingest Endpoints
// ─────────────────────────────────────────────────────────────

func (s *Server) ingestMetrics(c *fiber.Ctx) error {
	var req struct {
		Metrics []Metric `json:"metrics"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}

	for _, m := range req.Metrics {
		tags, _ := json.Marshal(m.Tags)
		_, err := s.db.Exec(`
			INSERT INTO metrics (timestamp, service, host, name, value, metric_type, tags)
			VALUES ($1, $2, $3, $4, $5, $6, $7)`,
			m.Timestamp, m.Service, m.Host, m.Name, m.Value, m.MetricType, tags)
		if err != nil {
			log.Printf("Error inserting metric: %v", err)
		}
	}

	// Update agent last_seen
	if len(req.Metrics) > 0 {
		host := req.Metrics[0].Host
		s.db.Exec(`UPDATE agents SET last_seen = NOW(), status = 'active' WHERE hostname = $1`, host)
	}

	return c.JSON(fiber.Map{"status": "received", "count": len(req.Metrics)})
}

func (s *Server) ingestLogs(c *fiber.Ctx) error {
	var req struct {
		Logs []LogEntry `json:"logs"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}

	for _, l := range req.Logs {
		_, err := s.db.Exec(`
			INSERT INTO logs (timestamp, service, host, level, message, trace_id, span_id, source, container_id)
			VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
			l.Timestamp, l.Service, l.Host, l.Level, l.Message, l.TraceID, l.SpanID, l.Source, l.ContainerID)
		if err != nil {
			log.Printf("Error inserting log: %v", err)
		}
	}

	return c.JSON(fiber.Map{"status": "received", "count": len(req.Logs)})
}

func (s *Server) ingestTraces(c *fiber.Ctx) error {
	var req struct {
		Spans []Span `json:"spans"`
		Host  string `json:"host"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}

	for _, sp := range req.Spans {
		attrs, _ := json.Marshal(sp.Attributes)
		_, err := s.db.Exec(`
			INSERT INTO spans (trace_id, span_id, parent_span_id, service, operation, start_time, end_time, duration_ns, status_code, status_message, kind, attributes)
			VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
			sp.TraceID, sp.SpanID, sp.ParentSpanID, sp.Service, sp.Operation, sp.StartTime, sp.EndTime, sp.DurationNs, sp.StatusCode, sp.StatusMsg, sp.Kind, attrs)
		if err != nil {
			log.Printf("Error inserting span: %v", err)
		}
	}

	return c.JSON(fiber.Map{"status": "received", "count": len(req.Spans)})
}

// ─────────────────────────────────────────────────────────────
// Query Endpoints
// ─────────────────────────────────────────────────────────────

func (s *Server) queryMetrics(c *fiber.Ctx) error {
	service := c.Query("service", "system")
	name := c.Query("name", "cpu.usage_percent")
	from := parseTime(c.Query("from"), time.Now().Add(-1*time.Hour))
	to := parseTime(c.Query("to"), time.Now())

	rows, err := s.db.Query(`
		SELECT timestamp, value FROM metrics
		WHERE service = $1 AND name = $2 AND timestamp >= $3 AND timestamp <= $4
		ORDER BY timestamp ASC
		LIMIT 1000`, service, name, from, to)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var metrics []map[string]any
	for rows.Next() {
		var ts time.Time
		var val float64
		if err := rows.Scan(&ts, &val); err == nil {
			metrics = append(metrics, map[string]any{"timestamp": ts, "value": val})
		}
	}

	return c.JSON(fiber.Map{"metrics": metrics, "count": len(metrics)})
}

func (s *Server) getMetricNames(c *fiber.Ctx) error {
	rows, err := s.db.Query(`SELECT DISTINCT name FROM metrics ORDER BY name`)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var names []string
	for rows.Next() {
		var name string
		if rows.Scan(&name) == nil {
			names = append(names, name)
		}
	}

	return c.JSON(fiber.Map{"names": names})
}

func (s *Server) queryLogs(c *fiber.Ctx) error {
	service := c.Query("service")
	level := c.Query("level")
	query := c.Query("q")
	from := parseTime(c.Query("from"), time.Now().Add(-1*time.Hour))
	to := parseTime(c.Query("to"), time.Now())
	limit := parseInt(c.Query("limit"), 100)

	sql := `SELECT timestamp, service, host, level, message, source, container_id 
			FROM logs WHERE timestamp >= $1 AND timestamp <= $2`
	args := []any{from, to}
	argN := 3

	if service != "" {
		sql += ` AND service = $` + strconv.Itoa(argN)
		args = append(args, service)
		argN++
	}
	if level != "" {
		sql += ` AND level = $` + strconv.Itoa(argN)
		args = append(args, level)
		argN++
	}
	if query != "" {
		sql += ` AND message ILIKE $` + strconv.Itoa(argN)
		args = append(args, "%"+query+"%")
		argN++
	}

	sql += ` ORDER BY timestamp DESC LIMIT $` + strconv.Itoa(argN)
	args = append(args, limit)

	rows, err := s.db.Query(sql, args...)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var logs []LogEntry
	for rows.Next() {
		var l LogEntry
		if err := rows.Scan(&l.Timestamp, &l.Service, &l.Host, &l.Level, &l.Message, &l.Source, &l.ContainerID); err == nil {
			logs = append(logs, l)
		}
	}

	return c.JSON(fiber.Map{"logs": logs, "count": len(logs)})
}

func (s *Server) queryTraces(c *fiber.Ctx) error {
	service := c.Query("service")
	from := parseTime(c.Query("from"), time.Now().Add(-1*time.Hour))
	to := parseTime(c.Query("to"), time.Now())
	limit := parseInt(c.Query("limit"), 20)

	sql := `SELECT DISTINCT trace_id, service, operation, MIN(start_time), MAX(duration_ns)
			FROM spans WHERE start_time >= $1 AND start_time <= $2`
	args := []any{from, to}
	argN := 3

	if service != "" {
		sql += ` AND service = $` + strconv.Itoa(argN)
		args = append(args, service)
		argN++
	}

	sql += ` GROUP BY trace_id, service, operation ORDER BY MIN(start_time) DESC LIMIT $` + strconv.Itoa(argN)
	args = append(args, limit)

	rows, err := s.db.Query(sql, args...)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var traces []map[string]any
	for rows.Next() {
		var traceID, service, operation string
		var startTime time.Time
		var durationNs int64
		if err := rows.Scan(&traceID, &service, &operation, &startTime, &durationNs); err == nil {
			traces = append(traces, map[string]any{
				"trace_id":    traceID,
				"service":     service,
				"operation":   operation,
				"start_time":  startTime,
				"duration_ns": durationNs,
			})
		}
	}

	return c.JSON(fiber.Map{"traces": traces, "count": len(traces)})
}

func (s *Server) getTrace(c *fiber.Ctx) error {
	traceID := c.Params("traceId")

	rows, err := s.db.Query(`
		SELECT trace_id, span_id, parent_span_id, service, operation, start_time, end_time, duration_ns, status_code, status_message, kind
		FROM spans WHERE trace_id = $1 ORDER BY start_time`, traceID)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var spans []Span
	for rows.Next() {
		var sp Span
		var parentSpanID, statusMsg sql.NullString
		if err := rows.Scan(&sp.TraceID, &sp.SpanID, &parentSpanID, &sp.Service, &sp.Operation, &sp.StartTime, &sp.EndTime, &sp.DurationNs, &sp.StatusCode, &statusMsg, &sp.Kind); err == nil {
			sp.ParentSpanID = parentSpanID.String
			sp.StatusMsg = statusMsg.String
			spans = append(spans, sp)
		}
	}

	if len(spans) == 0 {
		return c.Status(404).JSON(fiber.Map{"error": "trace not found"})
	}

	return c.JSON(fiber.Map{
		"trace_id":    traceID,
		"spans":       spans,
		"duration_ns": spans[len(spans)-1].EndTime.Sub(spans[0].StartTime).Nanoseconds(),
		"start_time":  spans[0].StartTime,
	})
}

func (s *Server) getServices(c *fiber.Ctx) error {
	rows, err := s.db.Query(`SELECT DISTINCT service FROM metrics UNION SELECT DISTINCT service FROM spans ORDER BY service`)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var services []string
	for rows.Next() {
		var svc string
		if rows.Scan(&svc) == nil {
			services = append(services, svc)
		}
	}

	return c.JSON(fiber.Map{"services": services})
}

// ─────────────────────────────────────────────────────────────
// Agents
// ─────────────────────────────────────────────────────────────

func (s *Server) getAgents(c *fiber.Ctx) error {
	rows, err := s.db.Query(`SELECT id, hostname, ip, version, status, last_seen, created_at FROM agents ORDER BY last_seen DESC`)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var agents []Agent
	for rows.Next() {
		var a Agent
		if err := rows.Scan(&a.ID, &a.Hostname, &a.IP, &a.Version, &a.Status, &a.LastSeen, &a.CreatedAt); err == nil {
			agents = append(agents, a)
		}
	}

	return c.JSON(fiber.Map{"agents": agents})
}

func (s *Server) registerAgent(c *fiber.Ctx) error {
	var agent Agent
	if err := c.BodyParser(&agent); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}

	agent.ID = uuid.New().String()
	agent.Status = "active"
	agent.LastSeen = time.Now()
	agent.CreatedAt = time.Now()

	_, err := s.db.Exec(`
		INSERT INTO agents (id, hostname, ip, version, status, last_seen, created_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
		ON CONFLICT (hostname) DO UPDATE SET ip = $3, version = $4, status = 'active', last_seen = NOW()`,
		agent.ID, agent.Hostname, agent.IP, agent.Version, agent.Status, agent.LastSeen, agent.CreatedAt)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}

	return c.JSON(fiber.Map{"status": "registered", "agent": agent})
}

// ─────────────────────────────────────────────────────────────
// Alerts
// ─────────────────────────────────────────────────────────────

func (s *Server) getAlerts(c *fiber.Ctx) error {
	status := c.Query("status")
	limit := parseInt(c.Query("limit"), 50)

	sql := `SELECT id, name, severity, service, host, message, status, fired_at, resolved_at FROM alerts`
	var args []any
	if status != "" {
		sql += ` WHERE status = $1`
		args = append(args, status)
	}
	sql += ` ORDER BY fired_at DESC LIMIT ` + strconv.Itoa(limit)

	rows, err := s.db.Query(sql, args...)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	defer rows.Close()

	var alerts []Alert
	for rows.Next() {
		var a Alert
		var resolvedAt *time.Time
		if err := rows.Scan(&a.ID, &a.Name, &a.Severity, &a.Service, &a.Host, &a.Message, &a.Status, &a.FiredAt, &resolvedAt); err == nil {
			a.ResolvedAt = resolvedAt
			alerts = append(alerts, a)
		}
	}

	return c.JSON(fiber.Map{"alerts": alerts})
}

func (s *Server) createAlert(c *fiber.Ctx) error {
	var alert Alert
	if err := c.BodyParser(&alert); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}

	alert.ID = uuid.New().String()
	alert.Status = "firing"
	alert.FiredAt = time.Now()

	_, err := s.db.Exec(`
		INSERT INTO alerts (id, name, severity, service, host, message, status, fired_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
		alert.ID, alert.Name, alert.Severity, alert.Service, alert.Host, alert.Message, alert.Status, alert.FiredAt)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}

	return c.JSON(fiber.Map{"status": "created", "alert": alert})
}

func (s *Server) resolveAlert(c *fiber.Ctx) error {
	alertID := c.Params("id")
	_, err := s.db.Exec(`UPDATE alerts SET status = 'resolved', resolved_at = NOW() WHERE id = $1`, alertID)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"status": "resolved"})
}

// ─────────────────────────────────────────────────────────────
// Dashboard
// ─────────────────────────────────────────────────────────────

func (s *Server) getDashboardOverview(c *fiber.Ctx) error {
	overview := fiber.Map{"timestamp": time.Now()}

	// Agents
	var totalAgents, activeAgents int
	s.db.QueryRow(`SELECT COUNT(*) FROM agents`).Scan(&totalAgents)
	s.db.QueryRow(`SELECT COUNT(*) FROM agents WHERE status = 'active'`).Scan(&activeAgents)
	overview["agents"] = fiber.Map{"total": totalAgents, "active": activeAgents}

	// Alerts
	var firingAlerts int
	s.db.QueryRow(`SELECT COUNT(*) FROM alerts WHERE status = 'firing'`).Scan(&firingAlerts)
	overview["alerts"] = fiber.Map{"firing": firingAlerts}

	// Services
	var serviceCount int
	s.db.QueryRow(`SELECT COUNT(DISTINCT service) FROM metrics`).Scan(&serviceCount)
	overview["services"] = fiber.Map{"count": serviceCount}

	return c.JSON(overview)
}

// ─────────────────────────────────────────────────────────────
// Background Jobs
// ─────────────────────────────────────────────────────────────

func (s *Server) runBackgroundJobs(ctx context.Context) {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			// Mark stale agents
			s.db.Exec(`UPDATE agents SET status = 'inactive' WHERE last_seen < NOW() - INTERVAL '5 minutes' AND status = 'active'`)
		}
	}
}

// ─────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────

func getEnv(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

func parseTime(s string, def time.Time) time.Time {
	if s == "" {
		return def
	}
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t
	}
	if ts, err := strconv.ParseInt(s, 10, 64); err == nil {
		return time.Unix(ts, 0)
	}
	if d, err := time.ParseDuration(s); err == nil {
		return time.Now().Add(-d)
	}
	return def
}

func parseInt(s string, def int) int {
	if s == "" {
		return def
	}
	if v, err := strconv.Atoi(s); err == nil {
		return v
	}
	return def
}

// agentAuthMiddleware creates middleware that accepts a simple agent key
func (s *Server) agentAuthMiddleware() fiber.Handler {
	agentKey := getEnv("AGENT_KEY", "")
	return func(c *fiber.Ctx) error {
		// If no agent key is configured, allow all (for backwards compat)
		if agentKey == "" {
			return c.Next()
		}

		authHeader := c.Get("Authorization")
		token := strings.TrimPrefix(authHeader, "Bearer ")

		// Check agent key
		if token == agentKey {
			return c.Next()
		}

		// Also check X-Agent-Key header
		if c.Get("X-Agent-Key") == agentKey {
			return c.Next()
		}

		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": "Invalid agent key",
		})
	}
}
