package api

import (
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"strings"
	"net/http"

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

// POST /api/v1/firmware/upload — Neues Agent-Binary hochladen
func (h *Handler) uploadFirmware(w http.ResponseWriter, r *http.Request) {
	version := r.URL.Query().Get("version")
	if version == "" {
		writeError(w, http.StatusBadRequest, "version Parameter erforderlich")
		return
	}

	platform := r.URL.Query().Get("platform")
	if platform == "" {
		platform = "freebsd" // Default fuer OPNsense
	}
	// Validieren
	validPlatforms := map[string]bool{"freebsd": true, "linux": true, "windows": true}
	if !validPlatforms[platform] {
		writeError(w, http.StatusBadRequest, "Ungueltige Plattform (freebsd, linux, windows)")
		return
	}

	// Binary lesen — Multipart (curl -F) oder raw Body (Content-Type: application/octet-stream)
	r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
	var binary []byte
	if ct := r.Header.Get("Content-Type"); strings.HasPrefix(ct, "multipart/") {
		if err := r.ParseMultipartForm(50 << 20); err != nil {
			writeError(w, http.StatusBadRequest, "Multipart-Parse fehlgeschlagen")
			return
		}
		file, _, err := r.FormFile("file")
		if err != nil {
			writeError(w, http.StatusBadRequest, "Feld 'file' fehlt im Multipart-Form")
			return
		}
		defer file.Close()
		binary, err = io.ReadAll(file)
		if err != nil {
			writeError(w, http.StatusBadRequest, "Binary lesen fehlgeschlagen")
			return
		}
	} else {
		var err error
		binary, err = io.ReadAll(r.Body)
		if err != nil {
			writeError(w, http.StatusBadRequest, "Binary lesen fehlgeschlagen")
			return
		}
	}

	if len(binary) == 0 {
		writeError(w, http.StatusBadRequest, "Leeres Binary")
		return
	}

	hash := sha256.Sum256(binary)
	hashStr := hex.EncodeToString(hash[:])

	if err := h.db.SaveAgentFirmware(version, platform, hashStr, binary); err != nil {
		log.Printf("Firmware speichern fehlgeschlagen: %v", err)
		writeError(w, http.StatusInternalServerError, "Speichern fehlgeschlagen")
		return
	}

	h.auditLog(r, "firmware.upload", "firmware", version, platform, fmt.Sprintf("%d bytes", len(binary)))
	log.Printf("Agent-Firmware v%s (%s) hochgeladen (%d bytes, Hash: %s)", version, platform, len(binary), hashStr[:16])

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"version":  version,
		"platform": platform,
		"hash":     hashStr,
		"size":     len(binary),
		"message":  "Firmware hochgeladen",
	})
}

// GET /api/v1/firmware — Firmware-Info (alle Plattformen oder spezifisch)
func (h *Handler) getFirmwareInfo(w http.ResponseWriter, r *http.Request) {
	platform := r.URL.Query().Get("platform")

	if platform == "" {
		// Alle Plattformen auflisten
		all, err := h.db.GetAllFirmwareInfo()
		if err != nil || len(all) == 0 {
			writeJSON(w, http.StatusOK, map[string]interface{}{
				"available": false,
				"message":   "Keine Firmware hochgeladen",
				"platforms": []interface{}{},
			})
			return
		}
		writeJSON(w, http.StatusOK, map[string]interface{}{
			"available": true,
			"platforms": all,
		})
		return
	}

	version, hash, size, err := h.db.GetFirmwareInfo(platform)
	if err != nil {
		writeJSON(w, http.StatusOK, map[string]interface{}{
			"available": false,
			"platform":  platform,
			"message":   "Keine Firmware fuer diese Plattform",
		})
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"available": true,
		"platform":  platform,
		"version":   version,
		"hash":      hash,
		"size":      size,
	})
}

// GET /api/v1/firmware/download — Binary herunterladen (fuer Updater)
func (h *Handler) downloadFirmware(w http.ResponseWriter, r *http.Request) {
	platform := r.URL.Query().Get("platform")
	if platform == "" {
		platform = "freebsd"
	}
	version, hash, binary, err := h.db.GetLatestFirmware(platform)
	if err != nil {
		writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar")
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", "attachment; filename=rmm-agent")
	w.Header().Set("X-Firmware-Version", version)
	w.Header().Set("X-Firmware-Hash", hash)
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(binary)))
	w.Write(binary)
}

// POST /api/v1/agents/{id}/request-update — Update-Flag setzen
func (h *Handler) requestUpdate(w http.ResponseWriter, r *http.Request) {
	agentID := r.PathValue("id")
	if err := h.db.SetUpdateRequest(agentID, true); err != nil {
		writeError(w, http.StatusInternalServerError, "Fehler beim Setzen des Update-Flags")
		return
	}
	h.auditLog(r, "firmware.update_request", "agent", agentID, "", "")
	log.Printf("Update angefordert fuer Agent %s", agentID)
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update angefordert", "agent_id": agentID})
}

// DELETE /api/v1/agents/{id}/request-update — Update-Flag loeschen
func (h *Handler) cancelUpdate(w http.ResponseWriter, r *http.Request) {
	agentID := r.PathValue("id")
	h.db.ClearUpdateRequest(agentID)
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update-Anforderung geloescht", "agent_id": agentID})
}

// POST /api/v1/agents/request-update-all — Update-Flag fuer alle setzen
func (h *Handler) requestUpdateAll(w http.ResponseWriter, r *http.Request) {
	count, err := h.db.SetUpdateRequestAll(true)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "Fehler")
		return
	}
	h.auditLog(r, "firmware.update_all", "", "", "", fmt.Sprintf("%d Agents", count))
	log.Printf("Update angefordert fuer %d Agents", count)
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": fmt.Sprintf("Update fuer %d Agents angefordert", count), "count": count})
}

// POST /api/v1/agents/{id}/agent-update — Update an einzelnen Agent pushen
func (h *Handler) pushAgentUpdate(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		// Plattform aus Request-Body oder Query-Parameter lesen, default linux
		platform := r.URL.Query().Get("platform")
		if platform == "" {
			var reqBody struct {
				Platform string `json:"platform"`
			}
			body, _ := io.ReadAll(r.Body)
			json.Unmarshal(body, &reqBody)
			platform = reqBody.Platform
		}
		if platform == "" {
			platform = "linux"
		}
		version, hash, binary, err := h.db.GetLatestFirmware(platform)
		if err != nil {
			writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar")
			return
		}

		// Agent-Version pruefen
		agent, _, err := h.db.GetAgent(agentID)
		if err != nil || agent == nil {
			writeError(w, http.StatusNotFound, "Agent nicht gefunden")
			return
		}

		if agent.AgentVersion == version {
			writeJSON(w, http.StatusOK, map[string]interface{}{
				"message":         "Agent ist bereits auf dem neuesten Stand",
				"current_version": agent.AgentVersion,
				"target_version":  version,
				"updated":         false,
			})
			return
		}

		// Binary als Base64 an Agent senden
		binaryB64 := base64.StdEncoding.EncodeToString(binary)

		cmdID := fmt.Sprintf("agent-update-%s", agentID[:8])
		cmd := map[string]interface{}{
			"type":    "command",
			"id":      cmdID,
			"command": "agent_update",
			"data": map[string]interface{}{
				"binary":      binaryB64,
				"hash":        hash,
				"version":     version,
				"old_version": agent.AgentVersion,
			},
		}

		cmdJSON, _ := json.Marshal(cmd)
		if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
			writeError(w, http.StatusBadGateway, fmt.Sprintf("Agent nicht erreichbar: %v", err))
			return
		}

		log.Printf("Agent-Update v%s an %s gesendet (%d bytes)", version, agent.Name, len(binary))

		writeJSON(w, http.StatusOK, map[string]interface{}{
			"message":         fmt.Sprintf("Update v%s an %s gesendet", version, agent.Name),
			"current_version": agent.AgentVersion,
			"target_version":  version,
			"updated":         true,
			"size":            len(binary),
		})
	}
}
