package api

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

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

var termUpgrader = websocket.Upgrader{
	ReadBufferSize:  4096,
	WriteBufferSize: 4096,
	CheckOrigin:     func(r *http.Request) bool { return true },
}

// TerminalHandler bridget Browser-WebSocket mit Agent-PTY (JWT-only, kein API-Key)
func (h *Handler) TerminalHandler(hub *ws.Hub) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		agentID := r.PathValue("id")
		if agentID == "" {
			http.Error(w, "agent_id erforderlich", http.StatusBadRequest)
			return
		}

		// Cols/Rows aus Query
		cols := uint16(220)
		rows := uint16(50)
		if c := r.URL.Query().Get("cols"); c != "" {
			var v uint16
			fmt.Sscanf(c, "%d", &v)
			if v > 0 {
				cols = v
			}
		}
		if r := r.URL.Query().Get("rows"); r != "" {
			var v uint16
			fmt.Sscanf(r, "%d", &v)
			if v > 0 {
				rows = v
			}
		}

		// WebSocket-Upgrade
		browserConn, err := termUpgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Printf("Terminal WS Upgrade fehlgeschlagen: %v", err)
			return
		}
		defer browserConn.Close()

		// PTY-Session-ID generieren
		sessionID := fmt.Sprintf("pty-%s-%d", agentID[:8], time.Now().UnixNano())

		// Output-Channel registrieren
		outputCh := hub.RegisterPTYSession(sessionID)
		defer hub.UnregisterPTYSession(sessionID)

		// pty_start an Agent senden
		err = h.sendCommandAndWaitPTY(hub, agentID, sessionID, cols, rows)
		if err != nil {
			log.Printf("PTY-Start fehlgeschlagen fuer %s: %v", agentID, err)
			browserConn.WriteMessage(websocket.TextMessage, []byte("\r\nFehler: Agent nicht erreichbar oder PTY-Start fehlgeschlagen\r\n"))
			return
		}

		log.Printf("PTY-Session gestartet: %s fuer Agent %s", sessionID, agentID)

		// Goroutine: Agent-Ausgabe -> Browser
		done := make(chan struct{})
		go func() {
			defer close(done)
			for {
				select {
				case data, ok := <-outputCh:
					if !ok {
						return
					}
					browserConn.SetWriteDeadline(time.Now().Add(10 * time.Second))
					if err := browserConn.WriteMessage(websocket.BinaryMessage, data); err != nil {
						return
					}
				case <-time.After(30 * time.Second):
					// Keep-alive ping
					if err := browserConn.WriteMessage(websocket.PingMessage, nil); err != nil {
						return
					}
				}
			}
		}()

		// Browser-Input -> Agent
		browserConn.SetReadDeadline(time.Time{}) // kein Timeout fuer interaktive Sessions
		for {
			messageType, data, err := browserConn.ReadMessage()
			if err != nil {
				break
			}

			if messageType == websocket.BinaryMessage {
				// Eingabe an Agent-PTY weiterleiten
				idBytes := []byte(sessionID)
				msg := make([]byte, 1+len(idBytes)+len(data))
				msg[0] = byte(len(idBytes))
				copy(msg[1:], idBytes)
				copy(msg[1+len(idBytes):], data)
				hub.SendBinaryToAgent(agentID, msg)

			} else if messageType == websocket.TextMessage {
				// JSON-Control-Messages (resize)
				var ctrl struct {
					Type string `json:"type"`
					Cols uint16 `json:"cols"`
					Rows uint16 `json:"rows"`
				}
				if err := json.Unmarshal(data, &ctrl); err == nil && ctrl.Type == "resize" {
					SendCommandToAgent(hub, agentID, "pty_resize", map[string]interface{}{
						"session_id": sessionID,
						"cols":       ctrl.Cols,
						"rows":       ctrl.Rows,
					})
				}
			}
		}

		// Browser disconnected -> PTY stoppen
		SendCommandToAgent(hub, agentID, "pty_stop", map[string]interface{}{
			"session_id": sessionID,
		})
		log.Printf("PTY-Session beendet: %s", sessionID)

		<-done
	}
}

func (h *Handler) sendCommandAndWaitPTY(hub *ws.Hub, agentID, sessionID string, cols, rows uint16) error {
	_, err := h.sendCommandAndWaitResult(hub, agentID, "pty_start", map[string]interface{}{
		"session_id": sessionID,
		"cols":       cols,
		"rows":       rows,
	}, 10)
	return err
}
