package api

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

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

func (h *Handler) setupTunnelRoutes(mux *http.ServeMux, hub *ws.Hub) {
	// Command-Endpoints
	mux.HandleFunc("POST /api/v1/agents/{id}/exec", h.executeCommand(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/update", h.agentUpdate(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/major-update", h.agentMajorUpdate(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/update-check", h.agentUpdateCheck(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/reboot", h.agentReboot(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/tunnel", h.openTunnel(hub))
	mux.HandleFunc("DELETE /api/v1/agents/{id}/tunnel/{tunnel_id}", h.closeTunnel(hub))
	mux.HandleFunc("GET /api/v1/agents/{id}/tunnels", h.listTunnels(hub))
	
	// HTTP-Proxy durch Tunnel
	mux.HandleFunc("GET /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
	mux.HandleFunc("PUT /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
	mux.HandleFunc("DELETE /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
	
	// Metrics-Endpoints
	mux.HandleFunc("GET /api/v1/agents/{id}/metrics", h.getMetrics())
	mux.HandleFunc("GET /api/v1/agents/{id}/metrics/summary", h.getMetricsSummary())

	// WireGuard-Endpoints
	mux.HandleFunc("POST /api/v1/agents/{id}/wireguard/peers", h.wgAddPeer(hub))
	mux.HandleFunc("GET /api/v1/agents/{id}/wireguard/peers", h.wgListPeers(hub))
	mux.HandleFunc("DELETE /api/v1/agents/{id}/wireguard/peers/{uuid}", h.wgDeletePeer(hub))

	// Backup-Endpoints
	mux.HandleFunc("POST /api/v1/agents/{id}/proxmox/action", h.proxmoxAction(hub))
	mux.HandleFunc("POST /api/v1/agents/{id}/backup", h.triggerBackup(hub))
	mux.HandleFunc("GET /api/v1/agents/{id}/backups", h.listBackups())
	mux.HandleFunc("GET /api/v1/agents/{id}/backups/{backup_id}", h.getBackup())
	mux.HandleFunc("DELETE /api/v1/agents/{id}/backups/{backup_id}", h.deleteBackup())
	mux.HandleFunc("GET /api/v1/agents/{id}/backups/diff", h.diffBackups())

	// Terminal-Endpoint (Browser -> Agent PTY)
	// Terminal wird NICHT hier registriert — wird in main.go unter JWTOnlyAuth eingebunden

	// WebSocket-Endpoint
	mux.HandleFunc("GET /api/v1/agent/ws", ws.NewWebSocketHandler(hub))
	
	// Hub-Status
	mux.HandleFunc("GET /api/v1/hub/status", h.hubStatus(hub))
}

func (h *Handler) executeCommand(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		var req struct {
			Command string `json:"command"`
			Timeout int    `json:"timeout,omitempty"`
		}

		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
			return
		}

		if req.Command == "" {
			writeError(w, http.StatusBadRequest, "Parameter 'command' erforderlich")
			return
		}

		if req.Timeout == 0 {
			req.Timeout = 30
		}

		// Audit
		cmdTrunc := req.Command
		if len(cmdTrunc) > 100 {
			cmdTrunc = cmdTrunc[:100]
		}
		h.auditLog(r, "exec", "agent", agentID, "", cmdTrunc)

		// Command senden und auf Antwort warten
		h.sendCommandAndWait(hub, agentID, "exec", map[string]interface{}{
			"command": req.Command,
			"timeout": req.Timeout,
		}, req.Timeout+5, w)
	}
}

func (h *Handler) openTunnel(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		
		// Request parsen
		var req struct {
			TargetHost string `json:"target_host"`
			TargetPort int    `json:"target_port"`
		}
		
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
			return
		}
		
		if req.TargetHost == "" || req.TargetPort <= 0 {
			writeError(w, http.StatusBadRequest, "target_host und target_port erforderlich")
			return
		}
		
		// Tunnel öffnen
		tunnel, err := hub.GetTunnelManager().OpenTunnel(agentID, req.TargetHost, req.TargetPort)
		if err != nil {
			log.Printf("Tunnel öffnen fehlgeschlagen: %v", err)
			writeError(w, http.StatusInternalServerError, err.Error())
			return
		}
		
		h.auditLog(r, "tunnel.open", "agent", agentID, "", fmt.Sprintf("%s:%d", req.TargetHost, req.TargetPort))

		// Response
		writeJSON(w, http.StatusCreated, map[string]interface{}{
			"tunnel_id":   tunnel.ID,
			"proxy_port":  tunnel.ProxyPort,
			"target_host": tunnel.TargetHost,
			"target_port": tunnel.TargetPort,
			"created_at":  tunnel.CreatedAt,
		})
	}
}

func (h *Handler) closeTunnel(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tunnelID := r.PathValue("tunnel_id")
		
		// Tunnel schließen
		if err := hub.GetTunnelManager().CloseTunnel(tunnelID); err != nil {
			writeError(w, http.StatusNotFound, err.Error())
			return
		}

		agentID := r.PathValue("id")
		h.auditLog(r, "tunnel.close", "agent", agentID, "", tunnelID)
		
		writeJSON(w, http.StatusOK, map[string]string{
			"message": "Tunnel geschlossen",
		})
	}
}

func (h *Handler) listTunnels(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		
		// Tunnel für Agent abrufen
		tunnels := hub.GetTunnelManager().GetAgentTunnels(agentID)
		
		// Response formatieren
		result := make([]map[string]interface{}, len(tunnels))
		for i, tunnel := range tunnels {
			result[i] = map[string]interface{}{
				"tunnel_id":   tunnel.ID,
				"proxy_port":  tunnel.ProxyPort,
				"target_host": tunnel.TargetHost,
				"target_port": tunnel.TargetPort,
				"active":      tunnel.Active,
				"created_at":  tunnel.CreatedAt,
			}
		}
		
		writeJSON(w, http.StatusOK, result)
	}
}

func (h *Handler) httpProxy(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tunnelID := r.PathValue("tunnel_id")
		
		// HTTP-Proxy durch Tunnel
		if err := hub.GetTunnelManager().CreateHTTPProxy(tunnelID, w, r); err != nil {
			writeError(w, http.StatusNotFound, err.Error())
			return
		}
	}
}

// --- Update Commands ---

func (h *Handler) agentUpdateCheck(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		h.auditLog(r, "update.check", "agent", agentID, "", "")
		h.sendCommandAndWait(hub, agentID, "update_check", nil, 120, w)
	}
}

func (h *Handler) agentUpdate(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		var req struct {
			Reboot bool `json:"reboot"`
		}
		json.NewDecoder(r.Body).Decode(&req)

		params := map[string]interface{}{
			"reboot": req.Reboot,
		}

		rebootStr := "ohne Reboot"
		if req.Reboot {
			rebootStr = "mit Reboot"
		}
		h.auditLog(r, "update.run", "agent", agentID, "", rebootStr)
		h.sendCommandAndWait(hub, agentID, "update", params, 900, w)
	}
}

func (h *Handler) agentMajorUpdate(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		var req struct {
			Version string `json:"version"`
			Reboot  bool   `json:"reboot"`
			Phase   string `json:"phase"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Version == "" {
			writeError(w, http.StatusBadRequest, "Parameter 'version' erforderlich (z.B. '26.1')")
			return
		}

		params := map[string]interface{}{
			"version": req.Version,
			"reboot":  req.Reboot,
		}
		if req.Phase != "" {
			params["phase"] = req.Phase
		}

		h.auditLog(r, "update.major", "agent", agentID, "", fmt.Sprintf("Version: %s, Phase: %s", req.Version, req.Phase))
		h.sendCommandAndWait(hub, agentID, "major_update", params, 1200, w)
	}
}

func (h *Handler) agentReboot(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		var req struct {
			Delay int `json:"delay"`
		}
		json.NewDecoder(r.Body).Decode(&req)

		params := map[string]interface{}{}
		if req.Delay > 0 {
			params["delay"] = req.Delay
		}

		h.auditLog(r, "reboot", "agent", agentID, "", "")
		h.sendCommandAndWait(hub, agentID, "reboot", params, 30, w)
	}
}

// proxmoxAction: VM/CT starten oder stoppen
func (h *Handler) proxmoxAction(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")

		var req struct {
			Type   string `json:"type"`   // "vm" oder "ct"
			VMID   int    `json:"vmid"`
			Action string `json:"action"` // "start", "stop", "shutdown"
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeError(w, http.StatusBadRequest, "Ungültiger Request-Body")
			return
		}
		if req.VMID <= 0 || req.Type == "" || req.Action == "" {
			writeError(w, http.StatusBadRequest, "type, vmid und action erforderlich")
			return
		}

		params := map[string]interface{}{
			"type":   req.Type,
			"vmid":   float64(req.VMID),
			"action": req.Action,
		}

		h.auditLog(r, "proxmox_action", "agent", agentID, "", fmt.Sprintf("%s %s %d", req.Action, req.Type, req.VMID))
		h.sendCommandAndWait(hub, agentID, "proxmox_action", params, 60, w)
	}
}

// sendAgentCommand erstellt einen einfachen Command-Handler ohne Request-Body
func (h *Handler) sendAgentCommand(hub *ws.Hub, command string, params map[string]interface{}, timeoutSec int) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		h.sendCommandAndWait(hub, agentID, command, params, timeoutSec, w)
	}
}

// sendCommandAndWait schickt ein Command an den Agent und wartet auf Response
func (h *Handler) sendCommandAndWait(hub *ws.Hub, agentID, command string, params map[string]interface{}, timeoutSec int, w http.ResponseWriter) {
	if !hub.IsAgentConnected(agentID) {
		writeError(w, http.StatusServiceUnavailable, "Agent nicht verbunden")
		return
	}

	cmdID := generateID()

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

	// 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("Command '%s' senden fehlgeschlagen: %v", command, err)
		writeError(w, http.StatusInternalServerError, "Command senden fehlgeschlagen")
		return
	}

	log.Printf("Command '%s' (ID: %s) an Agent %s gesendet, warte auf Response...", command, cmdID, agentID)

	// Auf Response warten
	select {
	case resp := <-responseCh:
		writeJSON(w, http.StatusOK, resp)
	case <-time.After(time.Duration(timeoutSec) * time.Second):
		writeError(w, http.StatusGatewayTimeout, "Timeout: Agent hat nicht rechtzeitig geantwortet")
	}
}

// sendCommandAndWaitResult wie sendCommandAndWait, gibt Response direkt zurueck (kein http.ResponseWriter)
func (h *Handler) sendCommandAndWaitResult(hub *ws.Hub, agentID, command string, params map[string]interface{}, timeoutSec int) (map[string]interface{}, error) {
	if !hub.IsAgentConnected(agentID) {
		return nil, fmt.Errorf("Agent nicht verbunden")
	}

	cmdID := generateID()
	cmd := map[string]interface{}{
		"type":    "command",
		"id":      cmdID,
		"command": command,
	}
	if params != nil {
		cmd["params"] = params
	}

	responseCh := hub.RegisterResponseWaiter(cmdID)
	defer hub.UnregisterResponseWaiter(cmdID)

	cmdJSON, _ := json.Marshal(cmd)
	if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
		return nil, err
	}

	select {
	case resp := <-responseCh:
		if status, _ := resp["status"].(string); status == "error" {
			if errMsg, _ := resp["error"].(string); errMsg != "" {
				return nil, fmt.Errorf(errMsg)
			}
		}
		return resp, nil
	case <-time.After(time.Duration(timeoutSec) * time.Second):
		return nil, fmt.Errorf("timeout")
	}
}

// SendCommandToAgent sendet ein Command ohne auf Response zu warten (fire-and-forget)
func SendCommandToAgent(hub *ws.Hub, agentID, command string, params map[string]interface{}) error {
	cmd := map[string]interface{}{
		"type":    "command",
		"id":      generateID(),
		"command": command,
	}
	if params != nil {
		cmd["params"] = params
	}
	cmdJSON, _ := json.Marshal(cmd)
	return hub.SendToAgent(agentID, cmdJSON)
}

func (h *Handler) hubStatus(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		stats := hub.GetAgentStats()
		connectedAgents := hub.GetConnectedAgents()
		
		response := map[string]interface{}{
			"status":           "running",
			"connected_agents": connectedAgents,
			"stats":            stats,
			"timestamp":        time.Now(),
		}
		
		writeJSON(w, http.StatusOK, response)
	}
}