package api

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/cynfo/rmm-backend/ws"
)

// POST /api/v1/agents/{id}/backup — Backup ausloesen (via WebSocket an Agent)
func (h *Handler) triggerBackup(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		if !hub.IsAgentConnected(agentID) {
			writeError(w, http.StatusServiceUnavailable, "Agent nicht verbunden")
			return
		}

		cmdID := generateID()

		cmd := map[string]interface{}{
			"type":    "command",
			"id":      cmdID,
			"command": "backup",
			"params":  map[string]interface{}{},
		}

		// Response-Channel registrieren
		responseCh := hub.RegisterResponseWaiter(cmdID)
		defer hub.UnregisterResponseWaiter(cmdID)

		cmdJSON, _ := json.Marshal(cmd)
		if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
			log.Printf("Backup-Command senden fehlgeschlagen: %v", err)
			writeError(w, http.StatusInternalServerError, "Command senden fehlgeschlagen")
			return
		}

		log.Printf("Backup-Command (ID: %s) an Agent %s gesendet", cmdID, agentID)

		// Auf Response warten (config.xml kann gross sein, 60s Timeout)
		select {
		case resp := <-responseCh:
			status, _ := resp["status"].(string)
			if status != "ok" {
				errMsg, _ := resp["error"].(string)
				writeError(w, http.StatusInternalServerError, fmt.Sprintf("Backup fehlgeschlagen: %s", errMsg))
				return
			}

			data, ok := resp["data"].(map[string]interface{})
			if !ok {
				writeError(w, http.StatusInternalServerError, "Ungueltiges Response-Format")
				return
			}

			// Config aus Base64 dekodieren
			configB64, _ := data["config"].(string)
			hash, _ := data["hash"].(string)
			sizeFloat, _ := data["size"].(float64)
			size := int(sizeFloat)

			configXML, err := base64.StdEncoding.DecodeString(configB64)
			if err != nil {
				writeError(w, http.StatusInternalServerError, "Base64-Dekodierung fehlgeschlagen")
				return
			}

			// In DB speichern (dedupliziert)
			backupID, isNew, err := h.db.SaveConfigBackup(agentID, hash, size, string(configXML))
			if err != nil {
				log.Printf("Backup speichern fehlgeschlagen: %v", err)
				writeError(w, http.StatusInternalServerError, "Backup speichern fehlgeschlagen")
				return
			}

			msg := "Backup gespeichert"
			if !isNew {
				msg = "Config unveraendert (Hash identisch)"
			}

			h.auditLog(r, "backup.create", "agent", agentID, "", fmt.Sprintf("backup_id=%d", backupID))

			writeJSON(w, http.StatusOK, map[string]interface{}{
				"backup_id": backupID,
				"hash":      hash,
				"size":      size,
				"new":       isNew,
				"message":   msg,
			})

		case <-time.After(60 * time.Second):
			writeError(w, http.StatusGatewayTimeout, "Timeout: Agent hat nicht rechtzeitig geantwortet")
		}
	}
}

// GET /api/v1/agents/{id}/backups — Alle Backups auflisten
func (h *Handler) listBackups() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		limit := 50
		if l := r.URL.Query().Get("limit"); l != "" {
			if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
				limit = parsed
			}
		}

		backups, err := h.db.GetConfigBackups(agentID, limit)
		if err != nil {
			log.Printf("Backups laden fehlgeschlagen: %v", err)
			writeError(w, http.StatusInternalServerError, "Fehler beim Laden")
			return
		}
		writeJSON(w, http.StatusOK, backups)
	}
}

// GET /api/v1/agents/{id}/backups/{backup_id} — Einzelnes Backup herunterladen
func (h *Handler) getBackup() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		backupIDStr := r.PathValue("backup_id")

		// "latest" als Spezialwert
		if backupIDStr == "latest" {
			backup, err := h.db.GetLatestConfigBackup(agentID)
			if err != nil {
				writeError(w, http.StatusInternalServerError, "Fehler beim Laden")
				return
			}
			if backup == nil {
				writeError(w, http.StatusNotFound, "Kein Backup vorhanden")
				return
			}

			// Format: raw XML oder JSON?
			if r.URL.Query().Get("format") == "xml" {
				w.Header().Set("Content-Type", "application/xml")
				w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=config_%s.xml", agentID[:8]))
				w.Write([]byte(backup["config_xml"].(string)))
				return
			}
			writeJSON(w, http.StatusOK, backup)
			return
		}

		backupID, err := strconv.ParseInt(backupIDStr, 10, 64)
		if err != nil {
			writeError(w, http.StatusBadRequest, "Ungueltige Backup-ID")
			return
		}

		backup, err := h.db.GetConfigBackup(agentID, backupID)
		if err != nil {
			writeError(w, http.StatusInternalServerError, "Fehler beim Laden")
			return
		}
		if backup == nil {
			writeError(w, http.StatusNotFound, "Backup nicht gefunden")
			return
		}

		h.auditLog(r, "backup.download", "agent", agentID, "", fmt.Sprintf("backup_id=%s", backupIDStr))

		// Raw XML Download
		if r.URL.Query().Get("format") == "xml" {
			w.Header().Set("Content-Type", "application/xml")
			w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=config_%s_%d.xml", agentID[:8], backupID))
			w.Write([]byte(backup["config_xml"].(string)))
			return
		}

		writeJSON(w, http.StatusOK, backup)
	}
}

// DELETE /api/v1/agents/{id}/backups/{backup_id} — Backup loeschen
func (h *Handler) deleteBackup() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		backupIDStr := r.PathValue("backup_id")

		backupID, err := strconv.ParseInt(backupIDStr, 10, 64)
		if err != nil {
			writeError(w, http.StatusBadRequest, "Ungueltige Backup-ID")
			return
		}

		deleted, err := h.db.DeleteConfigBackup(agentID, backupID)
		if err != nil {
			writeError(w, http.StatusInternalServerError, "Loeschen fehlgeschlagen")
			return
		}
		if !deleted {
			writeError(w, http.StatusNotFound, "Backup nicht gefunden")
			return
		}

		writeJSON(w, http.StatusOK, map[string]string{"message": "Backup geloescht"})
	}
}

// GET /api/v1/agents/{id}/backups/diff?a={id1}&b={id2} — Diff zwischen zwei Backups
func (h *Handler) diffBackups() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		aStr := r.URL.Query().Get("a")
		bStr := r.URL.Query().Get("b")
		if aStr == "" || bStr == "" {
			writeError(w, http.StatusBadRequest, "Parameter 'a' und 'b' (Backup-IDs) erforderlich")
			return
		}

		aID, err := strconv.ParseInt(aStr, 10, 64)
		if err != nil {
			writeError(w, http.StatusBadRequest, "Ungueltige Backup-ID 'a'")
			return
		}
		bID, err := strconv.ParseInt(bStr, 10, 64)
		if err != nil {
			writeError(w, http.StatusBadRequest, "Ungueltige Backup-ID 'b'")
			return
		}

		backupA, err := h.db.GetConfigBackup(agentID, aID)
		if err != nil || backupA == nil {
			writeError(w, http.StatusNotFound, fmt.Sprintf("Backup %d nicht gefunden", aID))
			return
		}
		backupB, err := h.db.GetConfigBackup(agentID, bID)
		if err != nil || backupB == nil {
			writeError(w, http.StatusNotFound, fmt.Sprintf("Backup %d nicht gefunden", bID))
			return
		}

		xmlA := backupA["config_xml"].(string)
		xmlB := backupB["config_xml"].(string)

		if backupA["hash"] == backupB["hash"] {
			writeJSON(w, http.StatusOK, map[string]interface{}{
				"identical": true,
				"a":         map[string]interface{}{"id": aID, "hash": backupA["hash"], "created_at": backupA["created_at"]},
				"b":         map[string]interface{}{"id": bID, "hash": backupB["hash"], "created_at": backupB["created_at"]},
				"diff":      "",
			})
			return
		}

		// Zeilenweises Diff berechnen
		diff := computeLineDiff(xmlA, xmlB)

		writeJSON(w, http.StatusOK, map[string]interface{}{
			"identical": false,
			"a":         map[string]interface{}{"id": aID, "hash": backupA["hash"], "created_at": backupA["created_at"]},
			"b":         map[string]interface{}{"id": bID, "hash": backupB["hash"], "created_at": backupB["created_at"]},
			"diff":      diff,
		})
	}
}

// computeLineDiff erzeugt ein einfaches zeilenweises Diff (unified-style)
func computeLineDiff(a, b string) string {
	linesA := strings.Split(a, "\n")
	linesB := strings.Split(b, "\n")

	var result []string
	maxLen := len(linesA)
	if len(linesB) > maxLen {
		maxLen = len(linesB)
	}

	// Einfaches sequenzielles Diff — fuer XML config.xml voellig ausreichend
	i, j := 0, 0
	for i < len(linesA) || j < len(linesB) {
		if i < len(linesA) && j < len(linesB) && linesA[i] == linesB[j] {
			// Gleiche Zeile — uebergehen (nur Context bei Aenderungen zeigen)
			i++
			j++
			continue
		}

		// Vorausschauen ob die Zeile weiter unten in der anderen Datei existiert
		foundInB := -1
		if i < len(linesA) {
			for k := j; k < len(linesB) && k < j+10; k++ {
				if linesA[i] == linesB[k] {
					foundInB = k
					break
				}
			}
		}

		foundInA := -1
		if j < len(linesB) {
			for k := i; k < len(linesA) && k < i+10; k++ {
				if linesB[j] == linesA[k] {
					foundInA = k
					break
				}
			}
		}

		if foundInB >= 0 && (foundInA < 0 || foundInB-j <= foundInA-i) {
			// Zeilen in B eingefuegt
			for j < foundInB {
				result = append(result, fmt.Sprintf("+ %s", linesB[j]))
				j++
			}
		} else if foundInA >= 0 {
			// Zeilen in A entfernt
			for i < foundInA {
				result = append(result, fmt.Sprintf("- %s", linesA[i]))
				i++
			}
		} else {
			// Zeile geaendert
			if i < len(linesA) {
				result = append(result, fmt.Sprintf("- %s", linesA[i]))
				i++
			}
			if j < len(linesB) {
				result = append(result, fmt.Sprintf("+ %s", linesB[j]))
				j++
			}
		}
	}

	return strings.Join(result, "\n")
}
