// Handlers pour la page paramètres : lecture/écriture de la configuration globale.
package api

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"

	"git.geronzi.fr/proxmoxPanel/core/backend/internal/audit"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/crypto"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/db"
	dockerclient "git.geronzi.fr/proxmoxPanel/core/backend/internal/docker"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/logbuffer"
	"github.com/go-chi/chi/v5"
)

// SettingsHandler contient les handlers de configuration.
type SettingsHandler struct {
	db          *db.DB
	auditLogger *audit.Logger
	encryptor   *crypto.Encryptor
	docker      *dockerclient.Client
}

// NewSettingsHandler crée un SettingsHandler.
func NewSettingsHandler(database *db.DB, auditLog *audit.Logger, enc *crypto.Encryptor, docker *dockerclient.Client) *SettingsHandler {
	return &SettingsHandler{db: database, auditLogger: auditLog, encryptor: enc, docker: docker}
}

// paramètres publics (non-sensibles) accessibles par les admins.
var publicSettings = []string{
	"instance_name",
	"public_url",
	"default_lang",
	"proxmox_url",
	"ssh_host",
	"ssh_username",
	"dashboard_shortcuts",
}

// paramètres sensibles : modifiables en écriture seule, stockés chiffrés.
var encryptedSettings = []string{"ssh_password", "proxmox_token"}

// GetAll retourne tous les paramètres publics de l'application.
// GET /api/settings
func (h *SettingsHandler) GetAll(w http.ResponseWriter, r *http.Request) {
	result := make(map[string]string)
	for _, key := range publicSettings {
		value, _, err := h.db.GetSetting(key)
		if err == nil {
			result[key] = value
		}
	}
	JSONResponse(w, http.StatusOK, result)
}

// UpdateSetting met à jour un paramètre spécifique.
// PUT /api/settings/{key}
// Body: { "value": "..." }
func (h *SettingsHandler) UpdateSetting(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)

	key := chi.URLParam(r, "key")
	if key == "" {
		JSONError(w, "Clé de paramètre manquante", http.StatusBadRequest)
		return
	}

	// Vérifier que la clé est modifiable (publique ou chiffrée)
	isPublic := false
	for _, k := range publicSettings {
		if k == key {
			isPublic = true
			break
		}
	}
	isEncrypted := false
	for _, k := range encryptedSettings {
		if k == key {
			isEncrypted = true
			break
		}
	}
	if !isPublic && !isEncrypted {
		JSONError(w, "Paramètre non modifiable via l'API", http.StatusForbidden)
		return
	}

	var body struct {
		Value string `json:"value"`
	}
	if err := decodeJSON(r, &body); err != nil {
		JSONError(w, "Corps de requête invalide", http.StatusBadRequest)
		return
	}

	// Paramètres chiffrés : valeur vide = ne pas modifier
	if isEncrypted {
		if body.Value == "" {
			JSONResponse(w, http.StatusOK, map[string]string{"message": "Aucun changement"})
			return
		}
		encrypted, err := h.encryptor.Encrypt(body.Value)
		if err != nil {
			JSONError(w, "Erreur chiffrement", http.StatusInternalServerError)
			return
		}
		if err := h.db.SetSetting(key, encrypted, true); err != nil {
			JSONError(w, "Erreur sauvegarde paramètre", http.StatusInternalServerError)
			return
		}
		h.auditLogger.Log(&claims.UserID, claims.Username, "setting_update", key,
			map[string]string{"key": key}, clientIP(r))
		JSONResponse(w, http.StatusOK, map[string]string{"message": "Paramètre mis à jour"})
		return
	}

	if err := h.db.SetSetting(key, body.Value, false); err != nil {
		JSONError(w, "Erreur sauvegarde paramètre", http.StatusInternalServerError)
		return
	}

	h.auditLogger.Log(&claims.UserID, claims.Username, "setting_update", key,
		map[string]string{"key": key}, clientIP(r))

	JSONResponse(w, http.StatusOK, map[string]string{"message": "Paramètre mis à jour"})
}

// moduleResp représente un module dans les réponses API, incluant les champs de navigation.
type moduleResp struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Version     string `json:"version"`
	IsCore      bool   `json:"is_core"`
	IsEnabled   bool   `json:"is_enabled"`
	HasBackend  bool   `json:"has_backend"`
	NavHref     string `json:"nav_href"`
	NavIcon     string `json:"nav_icon"`
	NavColor    string `json:"nav_color"`
	NavLabelKey string `json:"nav_label_key"`
}

// GetModules retourne la liste de tous les modules et leur état.
// GET /api/modules
func (h *SettingsHandler) GetModules(w http.ResponseWriter, r *http.Request) {
	rows, err := h.db.Query(`
		SELECT id, name, description, version, is_core, is_enabled, has_backend,
		       COALESCE(nav_href,''), COALESCE(nav_icon,''), COALESCE(nav_color,''), COALESCE(nav_label_key,'')
		FROM modules ORDER BY is_core DESC, name ASC
	`)
	if err != nil {
		JSONError(w, "Erreur lecture modules", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var modules []moduleResp
	for rows.Next() {
		var m moduleResp
		var isCore, isEnabled, hasBackend int
		rows.Scan(&m.ID, &m.Name, &m.Description, &m.Version, &isCore, &isEnabled, &hasBackend,
			&m.NavHref, &m.NavIcon, &m.NavColor, &m.NavLabelKey)
		m.IsCore = isCore == 1
		m.IsEnabled = isEnabled == 1
		m.HasBackend = hasBackend == 1
		modules = append(modules, m)
	}

	JSONResponse(w, http.StatusOK, modules)
}

// EnableModule active un module.
// POST /api/modules/{id}/enable
func (h *SettingsHandler) EnableModule(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)
	id := chi.URLParam(r, "id")

	result, err := h.db.Exec(`UPDATE modules SET is_enabled = 1 WHERE id = ?`, id)
	if err != nil {
		JSONError(w, "Erreur activation module", http.StatusInternalServerError)
		return
	}
	n, _ := result.RowsAffected()
	if n == 0 {
		JSONError(w, "Module introuvable", http.StatusNotFound)
		return
	}

	h.auditLogger.Log(&claims.UserID, claims.Username, "module_enable", id, nil, clientIP(r))

	// Si le module a du code backend, redémarrer le container pour que LoadActive() le prenne en compte.
	restart := h.triggerRestartIfBackend(id)
	JSONResponse(w, http.StatusOK, map[string]interface{}{"message": "Module activé", "restarting": restart})
}

// DisableModule désactive un module (ne peut pas désactiver les modules CORE).
// POST /api/modules/{id}/disable
func (h *SettingsHandler) DisableModule(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)
	id := chi.URLParam(r, "id")

	// Vérifier que ce n'est pas un module CORE
	var isCore int
	if err := h.db.QueryRow(`SELECT is_core FROM modules WHERE id = ?`, id).Scan(&isCore); err != nil {
		JSONError(w, "Module introuvable", http.StatusNotFound)
		return
	}
	if isCore == 1 {
		JSONError(w, "Les modules CORE ne peuvent pas être désactivés", http.StatusForbidden)
		return
	}

	h.db.Exec(`UPDATE modules SET is_enabled = 0 WHERE id = ?`, id)
	h.auditLogger.Log(&claims.UserID, claims.Username, "module_disable", id, nil, clientIP(r))

	restart := h.triggerRestartIfBackend(id)
	JSONResponse(w, http.StatusOK, map[string]interface{}{"message": "Module désactivé", "restarting": restart})
}

// triggerRestartIfBackend déclenche un redémarrage Docker si le module a du code backend.
// Retourne true si un redémarrage a été déclenché.
func (h *SettingsHandler) triggerRestartIfBackend(moduleID string) bool {
	if h.docker == nil || !h.docker.Available() {
		return false
	}
	var hasBackend int
	if err := h.db.QueryRow(`SELECT has_backend FROM modules WHERE id = ?`, moduleID).Scan(&hasBackend); err != nil {
		return false
	}
	if hasBackend == 1 {
		h.docker.Restart()
		return true
	}
	return false
}

// enabledBackendModuleIDs retourne les IDs de tous les modules installés avec has_backend=1.
// Ces modules doivent être compilés dans le binaire lors d'un rebuild.
func (h *SettingsHandler) enabledBackendModuleIDs() ([]string, error) {
	rows, err := h.db.Query(`SELECT id FROM modules WHERE has_backend = 1`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var ids []string
	for rows.Next() {
		var id string
		rows.Scan(&id)
		ids = append(ids, id)
	}
	return ids, nil
}

// GetLogs retourne les dernières lignes du log applicatif (tampon mémoire).
// GET /api/settings/logs?lines=200
func (h *SettingsHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
	n := 200
	if s := r.URL.Query().Get("lines"); s != "" {
		if v, err := strconv.Atoi(s); err == nil && v > 0 {
			n = v
		}
	}
	lines := logbuffer.Global.Lines(n)
	if lines == nil {
		lines = []string{}
	}
	JSONResponse(w, http.StatusOK, lines)
}

// GetAuditLog retourne le journal d'audit paginé.
// GET /api/settings/audit
func (h *SettingsHandler) GetAuditLog(w http.ResponseWriter, r *http.Request) {
	rows, err := h.db.Query(`
		SELECT id, username, action, resource, details, ip, created_at
		FROM audit_log ORDER BY created_at DESC LIMIT 100
	`)
	if err != nil {
		JSONError(w, "Erreur lecture audit", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	type entry struct {
		ID        int64   `json:"id"`
		Username  string  `json:"username"`
		Action    string  `json:"action"`
		Resource  *string `json:"resource,omitempty"`
		Details   *string `json:"details,omitempty"`
		IP        *string `json:"ip,omitempty"`
		CreatedAt string  `json:"created_at"`
	}

	var entries []entry
	for rows.Next() {
		var e entry
		var resource, details, ip *string
		rows.Scan(&e.ID, &e.Username, &e.Action, &resource, &details, &ip, &e.CreatedAt)
		e.Resource = resource
		e.Details = details
		e.IP = ip
		entries = append(entries, e)
	}

	JSONResponse(w, http.StatusOK, entries)
}

// RegistryModule représente un module disponible dans le store Forgejo.
type RegistryModule struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	RepoURL     string `json:"repo_url"`
	Installed   bool   `json:"installed"`
}

// registryResp est la réponse unifiée de /api/registry/modules.
type registryResp struct {
	Modules []RegistryModule `json:"modules"`
	Error   string           `json:"error,omitempty"`
}

// forgejoClient est un client HTTP avec timeout pour l'API Forgejo.
var forgejoClient = &http.Client{Timeout: 10 * time.Second}

// GetRegistryModules liste les repos de l'organisation sur Forgejo.
// Utilise GET /api/v1/orgs/{org}/repos (retourne un tableau JSON direct).
// Répond toujours 200 — l'erreur éventuelle est dans le champ "error".
// GET /api/registry/modules
func (h *SettingsHandler) GetRegistryModules(w http.ResponseWriter, r *http.Request) {
	// Récupérer les modules déjà installés en DB
	rows, err := h.db.Query(`SELECT id FROM modules`)
	if err != nil {
		JSONResponse(w, http.StatusOK, registryResp{Modules: []RegistryModule{}, Error: "Erreur lecture DB : " + err.Error()})
		return
	}
	installed := make(map[string]bool)
	for rows.Next() {
		var id string
		rows.Scan(&id)
		installed[id] = true
	}
	rows.Close()

	// Base URL et organisation configurables via variables d'environnement
	forgejoURL := envOr("FORGEJO_URL", "https://git.geronzi.fr")
	forgejoOrg := envOr("FORGEJO_ORG", "proxmoxPanel")

	// Appel à l'API Forgejo : /api/v1/orgs/{org}/repos retourne un tableau JSON direct
	apiURL := fmt.Sprintf("%s/api/v1/orgs/%s/repos?limit=50", forgejoURL, forgejoOrg)
	ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()

	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	resp, err := forgejoClient.Do(req)
	if err != nil {
		JSONResponse(w, http.StatusOK, registryResp{
			Modules: []RegistryModule{},
			Error:   fmt.Sprintf("Impossible de joindre le store (%s) : %v", forgejoURL, err),
		})
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		JSONResponse(w, http.StatusOK, registryResp{
			Modules: []RegistryModule{},
			Error:   fmt.Sprintf("Store a répondu HTTP %d : %s", resp.StatusCode, string(body)),
		})
		return
	}

	body, _ := io.ReadAll(resp.Body)

	// /api/v1/orgs/{org}/repos retourne directement un tableau []repo
	var repos []struct {
		Name        string `json:"name"`
		Description string `json:"description"`
		HTMLURL     string `json:"html_url"`
	}
	if err := json.Unmarshal(body, &repos); err != nil {
		JSONResponse(w, http.StatusOK, registryResp{
			Modules: []RegistryModule{},
			Error:   fmt.Sprintf("Réponse store invalide : %v", err),
		})
		return
	}

	modules := make([]RegistryModule, 0, len(repos))
	for _, repo := range repos {
		if repo.Name == "core" {
			continue
		}
		modules = append(modules, RegistryModule{
			ID:          repo.Name,
			Name:        repo.Name,
			Description: repo.Description,
			RepoURL:     repo.HTMLURL,
			Installed:   installed[repo.Name],
		})
	}

	JSONResponse(w, http.StatusOK, registryResp{Modules: modules})
}

// envOr retourne la valeur de la variable d'environnement ou la valeur par défaut.
func envOr(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

// moduleJSON représente le fichier module.json d'un module.
type moduleJSON struct {
	ID             string `json:"id"`
	Name           string `json:"name"`
	Description    string `json:"description"`
	Version        string `json:"version"`
	NavHref        string `json:"nav_href"`
	NavIcon        string `json:"nav_icon"`
	NavColor       string `json:"nav_color"`
	NavLabelKey    string `json:"nav_label_key"`
	CoreMinVersion string `json:"core_min_version"`
	HasBackend     bool   `json:"has_backend"` // true = nécessite un rebuild Docker pour compilation
}

// InstallRegistryModule installe un module depuis le store Forgejo.
// POST /api/registry/modules/{id}/install
func (h *SettingsHandler) InstallRegistryModule(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)
	id := chi.URLParam(r, "id")

	// Récupérer module.json depuis Forgejo
	forgejoURL := envOr("FORGEJO_URL", "https://git.geronzi.fr")
	forgejoOrg := envOr("FORGEJO_ORG", "proxmoxPanel")
	// Essayer d'abord la branche master puis main
	moduleJSONURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/raw/module.json?ref=master", forgejoURL, forgejoOrg, id)
	reqCtx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(reqCtx, "GET", moduleJSONURL, nil)
	resp, err := forgejoClient.Do(req)
	if err == nil && resp.StatusCode == http.StatusNotFound {
		// Retenter sur la branche main
		resp.Body.Close()
		moduleJSONURL = fmt.Sprintf("%s/api/v1/repos/%s/%s/raw/module.json?ref=main", forgejoURL, forgejoOrg, id)
		req2, _ := http.NewRequestWithContext(reqCtx, "GET", moduleJSONURL, nil)
		resp, err = forgejoClient.Do(req2)
	}
	if err != nil {
		JSONError(w, fmt.Sprintf("Impossible d'accéder au module %s : %v", id, err), http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusNotFound {
		JSONError(w, fmt.Sprintf("module.json introuvable pour %s", id), http.StatusNotFound)
		return
	}
	if resp.StatusCode != http.StatusOK {
		JSONError(w, fmt.Sprintf("Erreur récupération module.json : HTTP %d", resp.StatusCode), http.StatusBadGateway)
		return
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		JSONError(w, "Erreur lecture module.json", http.StatusBadGateway)
		return
	}

	var mod moduleJSON
	if err := json.Unmarshal(body, &mod); err != nil {
		JSONError(w, "Erreur parsing module.json", http.StatusBadGateway)
		return
	}

	// Valider l'ID
	if mod.ID == "" {
		mod.ID = id
	}
	if mod.Version == "" {
		mod.Version = "1.0.0"
	}

	// URL du repo
	repoURL := fmt.Sprintf("%s/%s/%s", envOr("FORGEJO_URL", "https://git.geronzi.fr"), envOr("FORGEJO_ORG", "proxmoxPanel"), id)

	hasBackend := 0
	if mod.HasBackend {
		hasBackend = 1
	}

	// Insérer ou remplacer en DB
	_, err = h.db.Exec(`
		INSERT INTO modules (id, name, description, version, is_core, is_enabled, has_backend,
		                     nav_href, nav_icon, nav_color, nav_label_key, repo_url, installed_at)
		VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
		ON CONFLICT(id) DO UPDATE SET
			name=excluded.name, description=excluded.description, version=excluded.version,
			has_backend=excluded.has_backend,
			nav_href=excluded.nav_href, nav_icon=excluded.nav_icon, nav_color=excluded.nav_color,
			nav_label_key=excluded.nav_label_key, repo_url=excluded.repo_url,
			installed_at=CURRENT_TIMESTAMP
	`, mod.ID, mod.Name, mod.Description, mod.Version, hasBackend,
		mod.NavHref, mod.NavIcon, mod.NavColor, mod.NavLabelKey, repoURL)
	if err != nil {
		JSONError(w, fmt.Sprintf("Erreur installation module : %v", err), http.StatusInternalServerError)
		return
	}

	h.auditLogger.Log(&claims.UserID, claims.Username, "module_install", mod.ID,
		map[string]string{"version": mod.Version, "has_backend": fmt.Sprintf("%v", mod.HasBackend)}, clientIP(r))

	// Si le module a du code backend : déclencher un rebuild Docker.
	// Le rebuild compile le nouveau module dans le binaire et recrée le container.
	rebuilding := false
	if mod.HasBackend && h.docker != nil && h.docker.Available() {
		moduleIDs, err := h.enabledBackendModuleIDs()
		if err == nil {
			h.docker.RebuildAndRestart(moduleIDs)
			rebuilding = true
		}
	}

	JSONResponse(w, http.StatusAccepted, map[string]interface{}{
		"message":    fmt.Sprintf("Module %s installé", mod.ID),
		"rebuilding": rebuilding,
	})
}

// ── Réparation ────────────────────────────────────────────────────────────

// repairModule est une entrée retournée par GetRepairStatus.
type repairModule struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	IsEnabled   bool   `json:"is_enabled"`
	HasBackend  bool   `json:"has_backend"`
	InstalledAt string `json:"installed_at"`
}

// GetRepairStatus retourne les modules non-core présents en DB (potentiellement fantômes).
// GET /api/repair/modules
func (h *SettingsHandler) GetRepairStatus(w http.ResponseWriter, r *http.Request) {
	rows, err := h.db.Query(
		`SELECT id, name, is_enabled, has_backend, COALESCE(installed_at,'') FROM modules WHERE is_core = 0`)
	if err != nil {
		JSONResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}
	defer rows.Close()

	modules := []repairModule{}
	for rows.Next() {
		var m repairModule
		rows.Scan(&m.ID, &m.Name, &m.IsEnabled, &m.HasBackend, &m.InstalledAt)
		modules = append(modules, m)
	}
	JSONResponse(w, http.StatusOK, map[string]interface{}{"modules": modules})
}

// ResetModule supprime un module non-core de la DB (permet de le réinstaller proprement).
// DELETE /api/repair/modules/{id}
func (h *SettingsHandler) ResetModule(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	res, err := h.db.Exec(`DELETE FROM modules WHERE id = ? AND is_core = 0`, id)
	if err != nil {
		JSONResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		JSONResponse(w, http.StatusNotFound, map[string]string{"error": "module introuvable ou module core"})
		return
	}
	claims := GetClaims(r)
	h.auditLogger.Log(&claims.UserID, claims.Username, "repair.reset_module", id, nil, clientIP(r))
	JSONResponse(w, http.StatusOK, map[string]string{"message": "Module supprimé de la DB"})
}
