package api

import (
	"bytes"
	"crypto/subtle"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"time"

	"gitea.dooplex.hu/admin/felhom-hub/internal/assets"
	"gitea.dooplex.hu/admin/felhom-hub/internal/configgen"
	"gitea.dooplex.hu/admin/felhom-hub/internal/notify"
	"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)

// ConfigTemplateProvider returns the controller.yaml template for config generation.
type ConfigTemplateProvider interface {
	Template() string
}

// Handler handles API endpoints for report ingest and customer queries.
type Handler struct {
	store            *store.Store
	apiKey           string
	resendAPIKey     string
	fromEmail        string
	logger           *log.Logger
	httpClient       *http.Client
	templateProvider ConfigTemplateProvider
	dispatcher       *notify.Dispatcher
	assetsMgr        *assets.Manager
}

// New creates a new API handler.
func New(store *store.Store, apiKey, resendAPIKey, fromEmail string, templateProvider ConfigTemplateProvider, logger *log.Logger) *Handler {
	return &Handler{
		store:            store,
		apiKey:           apiKey,
		resendAPIKey:     resendAPIKey,
		fromEmail:        fromEmail,
		logger:           logger,
		httpClient:       &http.Client{Timeout: 10 * time.Second},
		templateProvider: templateProvider,
	}
}

// SetDispatcher sets the notification dispatcher for event-triggered emails.
func (h *Handler) SetDispatcher(d *notify.Dispatcher) {
	h.dispatcher = d
}

// SetAssetManager sets the asset manager for serving app assets to controllers.
func (h *Handler) SetAssetManager(am *assets.Manager) {
	h.assetsMgr = am
}

// checkAuth verifies the Bearer token against the global API key or a per-customer API key.
// Returns true if authorized.
func (h *Handler) checkAuth(r *http.Request) bool {
	_, _, ok := h.checkAuthCustomer(r)
	return ok
}

// checkAuthCustomer verifies the Bearer token and returns the authenticated customer identity.
// For per-customer keys: returns (customerID, false, true).
// For global key: returns ("", true, true) — caller must allow any customer_id.
// On failure: returns ("", false, false).
func (h *Handler) checkAuthCustomer(r *http.Request) (customerID string, isGlobal bool, ok bool) {
	auth := r.Header.Get("Authorization")
	if !strings.HasPrefix(auth, "Bearer ") {
		return "", false, false
	}
	token := strings.TrimPrefix(auth, "Bearer ")

	// Check global key first
	if h.apiKey != "" && subtle.ConstantTimeCompare([]byte(token), []byte(h.apiKey)) == 1 {
		return "", true, true
	}

	// Check per-customer key
	cfg, err := h.store.GetCustomerConfigByAPIKey(token)
	if err != nil || cfg == nil {
		return "", false, false
	}
	return cfg.CustomerID, false, true
}

// ServeHTTP routes API requests.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := strings.TrimPrefix(r.URL.Path, "/api/v1")

	switch {
	case r.Method == http.MethodPost && path == "/report":
		h.handleReport(w, r)
	case r.Method == http.MethodPost && path == "/event":
		h.handleEvent(w, r)
	case r.Method == http.MethodPost && path == "/notify":
		h.handleNotify(w, r)
	case r.Method == http.MethodPost && path == "/infra-backup":
		h.handleInfraBackupPush(w, r)
	case r.Method == http.MethodGet && strings.HasPrefix(path, "/infra-backup/"):
		h.handleInfraBackupGet(w, r, strings.TrimPrefix(path, "/infra-backup/"))
	case r.Method == http.MethodPost && path == "/preferences":
		h.handleSavePreferences(w, r)
	case r.Method == http.MethodGet && path == "/customers":
		h.handleCustomers(w, r)
	case r.Method == http.MethodGet && strings.HasPrefix(path, "/customers/"):
		parts := strings.Split(strings.TrimPrefix(path, "/customers/"), "/")
		customerID := parts[0]
		if len(parts) > 1 && parts[1] == "history" {
			h.handleCustomerHistory(w, r, customerID)
		} else {
			h.handleCustomer(w, r, customerID)
		}
	case r.Method == http.MethodGet && strings.HasPrefix(path, "/recovery/"):
		customerID := strings.TrimPrefix(path, "/recovery/")
		h.handleRecovery(w, r, customerID)
	case r.Method == http.MethodGet && strings.HasPrefix(path, "/config/"):
		customerID := strings.TrimPrefix(path, "/config/")
		h.handleConfigRetrieve(w, r, customerID)
	case r.Method == http.MethodGet && path == "/assets/manifest":
		h.handleAssetsManifest(w, r)
	case r.Method == http.MethodGet && strings.HasPrefix(path, "/assets/file/"):
		filename := strings.TrimPrefix(path, "/assets/file/")
		h.handleAssetFile(w, r, filename)
	default:
		http.NotFound(w, r)
	}
}

func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
	authCustomerID, isGlobal, ok := h.checkAuthCustomer(r)
	if !ok {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 1MB limit
	if err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	// Extract customer_id from JSON
	var payload struct {
		CustomerID string `json:"customer_id"`
	}
	if err := json.Unmarshal(body, &payload); err != nil || payload.CustomerID == "" {
		http.Error(w, "Invalid payload: customer_id required", http.StatusBadRequest)
		return
	}

	// Validate customer_id matches authenticated customer (unless global key)
	if !isGlobal && authCustomerID != payload.CustomerID {
		http.Error(w, "Forbidden: customer_id mismatch", http.StatusForbidden)
		return
	}

	if err := h.store.SaveReport(payload.CustomerID, body); err != nil {
		h.logger.Printf("[ERROR] Failed to save report from %s: %v", payload.CustomerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	// Parse and save app telemetry (backward-compatible — old controllers won't have this field)
	var telemetryPayload struct {
		AppTelemetry []store.AppTelemetryRecord `json:"app_telemetry"`
	}
	if err := json.Unmarshal(body, &telemetryPayload); err == nil && len(telemetryPayload.AppTelemetry) > 0 {
		if err := h.store.SaveAppTelemetry(payload.CustomerID, time.Now(), telemetryPayload.AppTelemetry); err != nil {
			h.logger.Printf("[WARN] Failed to save app telemetry for %s: %v", payload.CustomerID, err)
		}
	}

	h.logger.Printf("[INFO] Received report from %s (%d bytes)", payload.CustomerID, len(body))

	// Build response with optional customer_blocked flag
	resp := map[string]interface{}{"status": "ok"}
	if custCfg, err := h.store.GetCustomerConfig(payload.CustomerID); err == nil && custCfg != nil {
		if custCfg.Status == "blocked" {
			resp["customer_blocked"] = true
		}
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(resp)
}

// allowedEventTypes lists all valid event_type values the Hub accepts.
var allowedEventTypes = map[string]bool{
	// Controller-pushed events
	"controller_started":          true,
	"controller_updated":          true,
	"backup_completed":            true,
	"backup_failed":               true,
	"db_dump_completed":           true,
	"db_dump_failed":              true,
	"backup_integrity_ok":         true,
	"backup_integrity_failed":     true,
	"crossdrive_completed":        true,
	"crossdrive_failed":           true,
	"storage_disconnected":        true,
	"storage_reconnected":         true,
	"disk_warning":                true,
	"disk_critical":               true,
	"health_degraded":             true,
	"health_critical":             true,
	"health_recovered":            true,
	"app_deployed":                true,
	"app_removed":                 true,
	"disaster_recovery_started":   true,
	"disaster_recovery_completed": true,
	// Hub-generated events
	"node_stale":              true,
	"node_down":               true,
	"node_recovered":          true,
	"expected_backup_missed":  true,
	"expected_dbdump_missed":  true,
	// Special
	"test": true,
}

// handleEvent processes structured events from controllers (new endpoint, replaces /notify for updated controllers).
func (h *Handler) handleEvent(w http.ResponseWriter, r *http.Request) {
	authCustomerID, isGlobal, ok := h.checkAuthCustomer(r)
	if !ok {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	var payload struct {
		CustomerID string          `json:"customer_id"`
		EventType  string          `json:"event_type"`
		Severity   string          `json:"severity"`
		Message    string          `json:"message"`
		Details    json.RawMessage `json:"details"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}
	if payload.CustomerID == "" || payload.EventType == "" {
		http.Error(w, "customer_id and event_type are required", http.StatusBadRequest)
		return
	}

	// Validate customer_id matches authenticated customer (unless global key)
	if !isGlobal && authCustomerID != payload.CustomerID {
		http.Error(w, "Forbidden: customer_id mismatch", http.StatusForbidden)
		return
	}

	// Validate event_type
	if !allowedEventTypes[payload.EventType] {
		http.Error(w, fmt.Sprintf("Invalid event_type: %s", payload.EventType), http.StatusBadRequest)
		return
	}

	// Validate/default severity
	switch payload.Severity {
	case "info", "warning", "error":
	default:
		payload.Severity = "info"
	}

	// Store details as JSON string
	detailsStr := "{}"
	if len(payload.Details) > 0 && string(payload.Details) != "null" {
		detailsStr = string(payload.Details)
	}

	_, err = h.store.SaveEvent(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, detailsStr, "controller")
	if err != nil {
		h.logger.Printf("[ERROR] Failed to save event from %s: %v", payload.CustomerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("[INFO] Event from %s: %s (%s) — %s", payload.CustomerID, payload.EventType, payload.Severity, payload.Message)

	// Dispatch notifications (non-blocking)
	if h.dispatcher != nil {
		go h.dispatcher.ProcessEvent(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, detailsStr, "controller")
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"ok":true}`))
}

func (h *Handler) handleCustomers(w http.ResponseWriter, r *http.Request) {
	customers, err := h.store.GetCustomers()
	if err != nil {
		h.logger.Printf("[ERROR] Failed to get customers: %v", err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	type customerJSON struct {
		ID                 string     `json:"id"`
		Name               string     `json:"name"`
		ControllerVersion  string     `json:"controller_version"`
		ControllerURL      string     `json:"controller_url,omitempty"`
		HealthStatus       string     `json:"health_status"`
		LastSeen           time.Time  `json:"last_seen"`
		CPUPercent         float64    `json:"cpu_percent"`
		MemoryPercent      float64    `json:"memory_percent"`
		ContainerTotal     int        `json:"container_total"`
		ContainerRunning   int        `json:"container_running"`
		BackupLastSnapshot *time.Time `json:"backup_last_snapshot"`
	}

	result := make([]customerJSON, 0, len(customers))
	for _, c := range customers {
		result = append(result, customerJSON{
			ID:                 c.CustomerID,
			Name:               c.CustomerName,
			ControllerVersion:  c.ControllerVersion,
			ControllerURL:      c.ControllerURL,
			HealthStatus:       c.HealthStatus,
			LastSeen:           c.ReceivedAt,
			CPUPercent:         c.CPUPercent,
			MemoryPercent:      c.MemoryPercent,
			ContainerTotal:     c.ContainerTotal,
			ContainerRunning:   c.ContainerRunning,
			BackupLastSnapshot: c.BackupLastSnapshot,
		})
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

func (h *Handler) handleCustomer(w http.ResponseWriter, r *http.Request, customerID string) {
	customer, err := h.store.GetCustomer(customerID)
	if err != nil {
		h.logger.Printf("[ERROR] Failed to get customer %s: %v", customerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}
	if customer == nil {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	// Return the full report JSON directly
	w.Write([]byte(customer.ReportJSON))
}

func (h *Handler) handleCustomerHistory(w http.ResponseWriter, r *http.Request, customerID string) {
	period := r.URL.Query().Get("period")
	var since time.Duration
	switch period {
	case "7d":
		since = 7 * 24 * time.Hour
	case "30d":
		since = 30 * 24 * time.Hour
	default:
		since = 24 * time.Hour
	}

	history, err := h.store.GetCustomerHistory(customerID, since)
	if err != nil {
		h.logger.Printf("[ERROR] Failed to get history for %s: %v", customerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	type historyEntry struct {
		ReceivedAt    time.Time `json:"received_at"`
		HealthStatus  string    `json:"health_status"`
		CPUPercent    float64   `json:"cpu_percent"`
		MemoryPercent float64   `json:"memory_percent"`
	}

	result := make([]historyEntry, 0, len(history))
	for _, h := range history {
		result = append(result, historyEntry{
			ReceivedAt:    h.ReceivedAt,
			HealthStatus:  h.HealthStatus,
			CPUPercent:    h.CPUPercent,
			MemoryPercent: h.MemoryPercent,
		})
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

// handleNotify processes notification events from customer controllers.
func (h *Handler) handleNotify(w http.ResponseWriter, r *http.Request) {
	if !h.checkAuth(r) {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	var payload struct {
		CustomerID string `json:"customer_id"`
		EventType  string `json:"event_type"`
		Severity   string `json:"severity"`
		Message    string `json:"message"`
		Details    string `json:"details"`
	}
	if err := json.Unmarshal(body, &payload); err != nil || payload.CustomerID == "" || payload.EventType == "" {
		http.Error(w, "Invalid payload: customer_id and event_type required", http.StatusBadRequest)
		return
	}

	h.logger.Printf("[INFO] Notification from %s: %s (%s) — %s", payload.CustomerID, payload.EventType, payload.Severity, payload.Message)

	// Check if customer is blocked
	if h.store.IsCustomerBlocked(payload.CustomerID) {
		h.logger.Printf("[INFO] Notification suppressed for blocked customer %s", payload.CustomerID)
		h.store.LogNotification(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, "skipped", "customer blocked", "customer")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"status":"ok","sent":false,"reason":"blocked"}`))
		return
	}

	// Look up customer notification preferences
	prefs, err := h.store.GetNotificationPrefs(payload.CustomerID)
	if err != nil {
		h.logger.Printf("[ERROR] Failed to get notification prefs for %s: %v", payload.CustomerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	// Check if customer has email configured and event type is enabled
	if prefs == nil || prefs.Email == "" {
		h.logger.Printf("[INFO] No email configured for %s, skipping notification", payload.CustomerID)
		h.store.LogNotification(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, "skipped", "no email configured", "customer")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"status":"ok","sent":false,"reason":"no_email"}`))
		return
	}

	// Check if event type is in the enabled list (test events always pass)
	eventEnabled := payload.EventType == "test"
	for _, e := range prefs.EnabledEvents {
		if e == payload.EventType {
			eventEnabled = true
			break
		}
	}
	if !eventEnabled {
		h.logger.Printf("[INFO] Event %s not enabled for %s, skipping", payload.EventType, payload.CustomerID)
		h.store.LogNotification(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, "skipped", "event not enabled", "customer")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"status":"ok","sent":false,"reason":"event_disabled"}`))
		return
	}

	// Send email via Resend API
	if h.resendAPIKey == "" {
		h.logger.Printf("[WARN] Resend API key not configured, cannot send notification email")
		h.store.LogNotification(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, "skipped", "resend api key not configured", "customer")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"status":"ok","sent":false,"reason":"no_api_key"}`))
		return
	}

	subject, emailBody := formatNotificationEmail(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, payload.Details)
	sendErr := h.sendResendEmail(prefs.Email, subject, emailBody)
	if sendErr != nil {
		h.logger.Printf("[ERROR] Failed to send notification email to %s: %v", prefs.Email, sendErr)
		h.store.LogNotification(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, "failed", sendErr.Error(), "customer")
		http.Error(w, "Failed to send email", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("[INFO] Notification email sent to %s for %s/%s", prefs.Email, payload.CustomerID, payload.EventType)
	h.store.LogNotification(payload.CustomerID, payload.EventType, payload.Severity, payload.Message, "sent", "", "customer")

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status":"ok","sent":true}`))
}

// handleSavePreferences stores notification preferences pushed from a customer controller.
func (h *Handler) handleSavePreferences(w http.ResponseWriter, r *http.Request) {
	if !h.checkAuth(r) {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	var payload struct {
		CustomerID    string   `json:"customer_id"`
		Email         string   `json:"email"`
		EnabledEvents []string `json:"enabled_events"`
		CooldownHours int      `json:"cooldown_hours"`
	}
	if err := json.Unmarshal(body, &payload); err != nil || payload.CustomerID == "" {
		http.Error(w, "Invalid payload: customer_id required", http.StatusBadRequest)
		return
	}

	if err := h.store.SaveNotificationPrefs(payload.CustomerID, payload.Email, payload.EnabledEvents, payload.CooldownHours); err != nil {
		h.logger.Printf("[ERROR] Failed to save notification prefs for %s: %v", payload.CustomerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("[INFO] Notification preferences updated for %s: email=%s, events=%v", payload.CustomerID, payload.Email, payload.EnabledEvents)
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status":"ok"}`))
}

// handleInfraBackupPush stores an infrastructure snapshot from a controller.
func (h *Handler) handleInfraBackupPush(w http.ResponseWriter, r *http.Request) {
	if !h.checkAuth(r) {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 1MB limit
	if err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	var payload struct {
		CustomerID string `json:"customer_id"`
	}
	if err := json.Unmarshal(body, &payload); err != nil || payload.CustomerID == "" {
		http.Error(w, "Invalid payload: customer_id required", http.StatusBadRequest)
		return
	}

	if err := h.store.SaveInfraBackup(payload.CustomerID, body); err != nil {
		h.logger.Printf("[ERROR] Failed to save infra backup for %s: %v", payload.CustomerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("[INFO] Infra backup saved for %s (%d bytes)", payload.CustomerID, len(body))
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status":"ok"}`))
}

// handleInfraBackupGet returns the infrastructure backup for a customer.
func (h *Handler) handleInfraBackupGet(w http.ResponseWriter, r *http.Request, customerID string) {
	if !h.checkAuth(r) {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if customerID == "" {
		http.Error(w, "Missing customer_id", http.StatusBadRequest)
		return
	}

	data, err := h.store.GetInfraBackup(customerID)
	if err != nil {
		h.logger.Printf("[ERROR] Failed to get infra backup for %s: %v", customerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}
	if data == nil {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

// handleRecovery returns both the generated controller.yaml and the infra backup for disaster recovery.
// Auth: X-Retrieval-Password header (same as config retrieval).
func (h *Handler) handleRecovery(w http.ResponseWriter, r *http.Request, customerID string) {
	if customerID == "" {
		http.Error(w, "Missing customer_id", http.StatusBadRequest)
		return
	}

	password := r.Header.Get("X-Retrieval-Password")
	if password == "" {
		http.Error(w, "Unauthorized: X-Retrieval-Password header required", http.StatusUnauthorized)
		return
	}

	cfg, err := h.store.GetCustomerConfig(customerID)
	if err != nil {
		h.logger.Printf("[ERROR] Recovery: failed to get customer config for %s: %v", customerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}
	if cfg == nil {
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}

	if subtle.ConstantTimeCompare([]byte(password), []byte(cfg.RetrievalPassword)) != 1 {
		http.Error(w, "Unauthorized: invalid password", http.StatusUnauthorized)
		return
	}

	// Generate controller.yaml
	var configYAML string
	if h.templateProvider != nil {
		yamlOutput, err := configgen.Generate(h.templateProvider.Template(), cfg)
		if err != nil {
			h.logger.Printf("[ERROR] Recovery: failed to generate config for %s: %v", customerID, err)
			http.Error(w, "Internal error", http.StatusInternalServerError)
			return
		}
		configYAML = yamlOutput
	}

	// Fetch infra backup (optional — may not exist for new customers)
	var infraBackup json.RawMessage
	hasInfraBackup := false
	if data, err := h.store.GetInfraBackup(customerID); err == nil && data != nil {
		infraBackup = data
		hasInfraBackup = true
	}

	resp := struct {
		CustomerID     string          `json:"customer_id"`
		ConfigYAML     string          `json:"config_yaml"`
		InfraBackup    json.RawMessage `json:"infra_backup"`
		HasInfraBackup bool            `json:"has_infra_backup"`
	}{
		CustomerID:     customerID,
		ConfigYAML:     configYAML,
		InfraBackup:    infraBackup,
		HasInfraBackup: hasInfraBackup,
	}

	h.logger.Printf("[INFO] Recovery data downloaded for customer %s (has_infra_backup=%v)", customerID, hasInfraBackup)
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// handleConfigRetrieve returns a generated controller.yaml for a customer.
// Auth: X-Retrieval-Password header (not Bearer token).
func (h *Handler) handleConfigRetrieve(w http.ResponseWriter, r *http.Request, customerID string) {
	if customerID == "" {
		http.Error(w, "Missing customer_id", http.StatusBadRequest)
		return
	}

	password := r.Header.Get("X-Retrieval-Password")
	if password == "" {
		http.Error(w, "Unauthorized: X-Retrieval-Password header required", http.StatusUnauthorized)
		return
	}

	cfg, err := h.store.GetCustomerConfig(customerID)
	if err != nil {
		h.logger.Printf("[ERROR] Failed to get customer config for %s: %v", customerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}
	if cfg == nil {
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}

	// Constant-time comparison to prevent timing attacks
	if subtle.ConstantTimeCompare([]byte(password), []byte(cfg.RetrievalPassword)) != 1 {
		http.Error(w, "Unauthorized: invalid password", http.StatusUnauthorized)
		return
	}

	if h.templateProvider == nil {
		http.Error(w, "Config generation not available", http.StatusServiceUnavailable)
		return
	}

	yamlOutput, err := configgen.Generate(h.templateProvider.Template(), cfg)
	if err != nil {
		h.logger.Printf("[ERROR] Failed to generate config for %s: %v", customerID, err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("[INFO] Config downloaded for customer %s", customerID)
	w.Header().Set("Content-Type", "text/yaml; charset=utf-8")
	w.Write([]byte(yamlOutput))
}

// sendResendEmail sends an email via the Resend HTTP API.
func (h *Handler) sendResendEmail(to, subject, textBody string) error {
	payload := map[string]interface{}{
		"from":    h.fromEmail,
		"to":      []string{to},
		"subject": subject,
		"text":    textBody,
	}

	jsonData, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("marshaling email payload: %w", err)
	}

	req, err := http.NewRequest("POST", "https://api.resend.com/emails", bytes.NewReader(jsonData))
	if err != nil {
		return fmt.Errorf("creating request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+h.resendAPIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := h.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("sending request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return fmt.Errorf("resend API returned %d: %s", resp.StatusCode, string(respBody))
	}

	return nil
}

// formatNotificationEmail creates a Hungarian email subject and body.
func formatNotificationEmail(customerID, eventType, severity, message, details string) (string, string) {
	severityLabel := map[string]string{
		"info":     "Információ",
		"warning":  "Figyelmeztetés",
		"error":    "Hiba",
		"critical": "Kritikus",
	}
	label := severityLabel[severity]
	if label == "" {
		label = severity
	}

	subject := fmt.Sprintf("[Felhom] %s: %s", label, message)

	now := time.Now().Format("2006-01-02 15:04")
	emailText := fmt.Sprintf(`Kedves Ügyfél!

A Felhom rendszered a következő figyelmeztetést jelezte:

%s

Részletek:
- Szerver: %s
- Időpont: %s
- Szint: %s
- Típus: %s`, message, customerID, now, label, eventType)

	if details != "" {
		emailText += fmt.Sprintf("\n- Megjegyzés: %s", details)
	}

	emailText += `

Ha kérdésed van, vedd fel a kapcsolatot az üzemeltetővel.

Üdvözlettel,
Felhom.eu monitoring`

	return subject, emailText
}

// --- Asset endpoints ---

func (h *Handler) handleAssetsManifest(w http.ResponseWriter, r *http.Request) {
	if !h.checkAuth(r) {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if h.assetsMgr == nil {
		http.Error(w, "Assets not configured", http.StatusServiceUnavailable)
		return
	}

	data, err := h.assetsMgr.MarshalManifestJSON()
	if err != nil {
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func (h *Handler) handleAssetFile(w http.ResponseWriter, r *http.Request, filename string) {
	if !h.checkAuth(r) {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if h.assetsMgr == nil {
		http.Error(w, "Assets not configured", http.StatusServiceUnavailable)
		return
	}

	h.assetsMgr.ServeFile(w, r, filename)
}
