// Handlers pour les mises à jour de paquets apt.
// Supporte : host Proxmox, un LXC spécifique, ou tous les LXC.
// La sortie est streamée ligne par ligne via WebSocket.
package api

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"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"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/ssh"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/websocket"
	"github.com/go-chi/chi/v5"
)

// UpdatesHandler contient les handlers de mises à jour.
type UpdatesHandler struct {
	db          *db.DB
	sshPool     *ssh.Pool
	hub         *websocket.Hub
	auditLogger *audit.Logger
	encryptor   *crypto.Encryptor
}

// NewUpdatesHandler crée un UpdatesHandler.
func NewUpdatesHandler(database *db.DB, sshPool *ssh.Pool, hub *websocket.Hub, auditLog *audit.Logger, enc *crypto.Encryptor) *UpdatesHandler {
	return &UpdatesHandler{
		db:          database,
		sshPool:     sshPool,
		hub:         hub,
		auditLogger: auditLog,
		encryptor:   enc,
	}
}

// sshCredentials récupère les credentials SSH depuis la configuration SQLite.
func (h *UpdatesHandler) sshCredentials() (host, user, pass string, err error) {
	host, _, _ = h.db.GetSetting("ssh_host")
	user, _, _ = h.db.GetSetting("ssh_username")
	encPass, _, _ := h.db.GetSetting("ssh_password")
	if encPass != "" {
		pass, err = h.encryptor.Decrypt(encPass)
		if err != nil {
			return "", "", "", fmt.Errorf("impossible de déchiffrer le mot de passe SSH")
		}
	}
	if host == "" || user == "" || pass == "" {
		return "", "", "", fmt.Errorf("SSH non configuré")
	}
	return host, user, pass, nil
}

// GetTargets retourne la liste des cibles disponibles : host Proxmox + tous les LXC.
// GET /api/updates/targets
func (h *UpdatesHandler) GetTargets(w http.ResponseWriter, r *http.Request) {
	sshHost, sshUser, sshPass, err := h.sshCredentials()
	if err != nil {
		JSONError(w, err.Error(), http.StatusServiceUnavailable)
		return
	}

	type Target struct {
		ID     string `json:"id"`
		Name   string `json:"name"`
		Status string `json:"status"`
		VMID   int    `json:"vmid,omitempty"`
	}

	targets := []Target{
		{ID: "host", Name: "Proxmox Host", Status: "running"},
	}

	output, err := h.sshPool.RunCommand(sshHost, sshUser, sshPass, "sudo -n /usr/sbin/pct list 2>/dev/null")
	if err == nil {
		for _, line := range strings.Split(output, "\n") {
			line = strings.TrimSpace(line)
			if line == "" {
				continue
			}
			fields := strings.Fields(line)
			if len(fields) < 2 || fields[0] == "VMID" {
				continue
			}
			vmid, parseErr := strconv.Atoi(fields[0])
			if parseErr != nil {
				continue
			}
			status := fields[1]
			name := fields[0] // fallback VMID si pas de nom
			if len(fields) >= 3 {
				name = fields[len(fields)-1]
			}
			targets = append(targets, Target{
				ID:     fmt.Sprintf("lxc:%d", vmid),
				Name:   name,
				Status: status,
				VMID:   vmid,
			})
		}
	}

	JSONResponse(w, http.StatusOK, targets)
}

// GetPackages retourne la liste des paquets pouvant être mis à jour pour une cible.
// GET /api/updates/packages?target=host|lxc:100
func (h *UpdatesHandler) GetPackages(w http.ResponseWriter, r *http.Request) {
	target := r.URL.Query().Get("target")
	if target == "" {
		JSONError(w, "Paramètre 'target' requis", http.StatusBadRequest)
		return
	}

	sshHost, sshUser, sshPass, err := h.sshCredentials()
	if err != nil {
		JSONError(w, err.Error(), http.StatusServiceUnavailable)
		return
	}

	var command string
	switch {
	case target == "host":
		command = "apt list --upgradable 2>/dev/null"
	case len(target) > 4 && target[:4] == "lxc:":
		lxcID := target[4:]
		command = fmt.Sprintf("sudo -n /usr/sbin/pct exec %s -- apt list --upgradable 2>/dev/null", lxcID)
	default:
		JSONError(w, "Cible invalide", http.StatusBadRequest)
		return
	}

	output, err := h.sshPool.RunCommand(sshHost, sshUser, sshPass, command)
	packages := parseAptPackages(output)
	if err != nil && len(packages) == 0 {
		log.Printf("[updates/packages] Erreur SSH pour %s : %v", target, err)
		JSONError(w, "Erreur SSH : "+err.Error(), http.StatusBadGateway)
		return
	}

	JSONResponse(w, http.StatusOK, packages)
}

// parseAptPackages analyse la sortie de `apt list --upgradable`.
func parseAptPackages(output string) []map[string]string {
	var packages []map[string]string
	for _, line := range strings.Split(output, "\n") {
		line = strings.TrimSpace(line)
		if !strings.Contains(line, "[upgradable from:") {
			continue
		}
		// Format : name/repo version arch [upgradable from: old_version]
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		name := strings.SplitN(parts[0], "/", 2)[0]
		version := parts[1]
		oldVersion := ""
		if idx := strings.Index(line, "upgradable from: "); idx >= 0 {
			oldVersion = strings.TrimRight(line[idx+len("upgradable from: "):], "] ")
		}
		packages = append(packages, map[string]string{
			"name":        name,
			"version":     version,
			"old_version": oldVersion,
		})
	}
	if packages == nil {
		packages = []map[string]string{}
	}
	return packages
}

// RunUpdate lance une mise à jour apt sur la cible spécifiée.
// POST /api/updates/run
// Body: { "target": "host" | "lxc:100" | "all" }
func (h *UpdatesHandler) RunUpdate(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)

	var body struct {
		Target string `json:"target"`
	}
	if err := decodeJSON(r, &body); err != nil || body.Target == "" {
		JSONError(w, "Paramètre 'target' requis (host, lxc:ID, ou all)", http.StatusBadRequest)
		return
	}

	// Récupérer les credentials SSH depuis les settings
	sshHost, _, _ := h.db.GetSetting("ssh_host")
	sshUser, _, _ := h.db.GetSetting("ssh_username")
	encryptedPass, _, _ := h.db.GetSetting("ssh_password")
	sshPass, _ := h.encryptor.Decrypt(encryptedPass)
	log.Printf("[updates/run] Credentials SSH — host=%s user=%s password_len=%d", sshHost, sshUser, len(sshPass))
	if sshHost == "" || sshUser == "" || sshPass == "" {
		log.Printf("[updates/run] SSH non configuré — host=%q user=%q password_empty=%v", sshHost, sshUser, sshPass == "")
		JSONError(w, "SSH non configuré", http.StatusServiceUnavailable)
		return
	}

	// Générer un ID de job unique
	jobID := generateJobID()

	// Enregistrer le job en base
	h.db.Exec(`
		INSERT INTO update_history (job_id, target, status, started_by) VALUES (?, ?, 'running', ?)
	`, jobID, body.Target, claims.UserID)

	h.auditLogger.Log(&claims.UserID, claims.Username, "update_start", body.Target, nil, clientIP(r))

	log.Printf("[updates/run] Job %s démarré — target=%s user=%d", jobID, body.Target, claims.UserID)
	go h.executeUpdate(jobID, body.Target, sshHost, sshUser, sshPass, claims.UserID)

	JSONResponse(w, http.StatusAccepted, map[string]string{
		"job_id":  jobID,
		"message": "Mise à jour démarrée",
	})
}

// GetHistory retourne l'historique des mises à jour.
// GET /api/updates/history
func (h *UpdatesHandler) GetHistory(w http.ResponseWriter, r *http.Request) {
	rows, err := h.db.Query(`
		SELECT job_id, target, status, output, started_at, finished_at
		FROM update_history
		ORDER BY started_at DESC
		LIMIT 50
	`)
	if err != nil {
		JSONError(w, "Erreur lecture historique", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	type entry struct {
		JobID      string  `json:"job_id"`
		Target     string  `json:"target"`
		Status     string  `json:"status"`
		Output     string  `json:"output"`
		StartedAt  string  `json:"started_at"`
		FinishedAt *string `json:"finished_at,omitempty"`
	}

	var entries []entry
	for rows.Next() {
		var e entry
		var finishedAt *string
		rows.Scan(&e.JobID, &e.Target, &e.Status, &e.Output, &e.StartedAt, &finishedAt)
		e.FinishedAt = finishedAt
		entries = append(entries, e)
	}

	JSONResponse(w, http.StatusOK, entries)
}

// WebSocketUpdate ouvre un WebSocket pour suivre un job de mise à jour en temps réel.
// GET /ws/updates/{jobId}
func (h *UpdatesHandler) WebSocketUpdate(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}

	claims := GetClaims(r)
	var userID int64
	if claims != nil {
		userID = claims.UserID
	}

	jobID := chi.URLParam(r, "jobId")
	wsClient := h.hub.NewClient(conn, userID)
	wsClient.Subscribe("update:" + jobID)
}

// executeUpdate exécute la commande apt et streame la sortie via WebSocket.
func (h *UpdatesHandler) executeUpdate(jobID, target, sshHost, sshUser, sshPass string, userID int64) {
	outputChan := make(chan string, 100)
	var command string

	switch {
	case target == "host":
		command = "DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y"

	case len(target) > 4 && target[:4] == "lxc:":
		lxcID := target[4:]
		command = fmt.Sprintf(
			"sudo -n /usr/sbin/pct exec %s -- bash -c 'DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y'",
			lxcID,
		)

	case target == "all":
		command = `for ct in $(sudo -n /usr/sbin/pct list | awk 'NR>1 {print $1}'); do
			echo "=== LXC $ct ==="
			sudo -n /usr/sbin/pct exec $ct -- bash -c 'DEBIAN_FRONTEND=noninteractive apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y' 2>/dev/null || echo "SKIP LXC $ct"
		done`

	default:
		h.db.Exec(`UPDATE update_history SET status='error', output=?, finished_at=CURRENT_TIMESTAMP WHERE job_id=?`,
			"Cible invalide : "+target, jobID)
		return
	}

	// Lancer le streaming SSH
	cmdPreview := command
	if len(cmdPreview) > 80 {
		cmdPreview = cmdPreview[:80] + "..."
	}
	log.Printf("[updates/execute] Job %s — SSH %s@%s commande: %s", jobID, sshUser, sshHost, cmdPreview)
	err := h.sshPool.StreamCommand(sshHost, sshUser, sshPass, command, outputChan)
	if err != nil {
		log.Printf("[updates/execute] Job %s — Erreur SSH : %v", jobID, err)
		h.db.Exec(`UPDATE update_history SET status='error', output=?, finished_at=CURRENT_TIMESTAMP WHERE job_id=?`,
			"Erreur SSH : "+err.Error(), jobID)
		h.hub.Publish("update:"+jobID, "update_error", map[string]string{"error": err.Error()})
		return
	}

	// Collecter la sortie et la publier ligne par ligne
	var fullOutput string
	for chunk := range outputChan {
		fullOutput += chunk
		h.hub.Publish("update:"+jobID, "update_output", map[string]string{"chunk": chunk})
	}

	// Finaliser le job
	log.Printf("[updates/execute] Job %s — terminé (%d octets de sortie)", jobID, len(fullOutput))
	h.db.Exec(`UPDATE update_history SET status='success', output=?, finished_at=CURRENT_TIMESTAMP WHERE job_id=?`,
		fullOutput, jobID)
	h.hub.Publish("update:"+jobID, "update_done", map[string]string{"job_id": jobID})
}

// generateJobID génère un identifiant unique pour un job de mise à jour.
func generateJobID() string {
	const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
	b := make([]byte, 8)
	for i := range b {
		b[i] = chars[rand.Intn(len(chars))]
	}
	return fmt.Sprintf("%d-%s", time.Now().Unix(), string(b))
}
