package session

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

const (
	defaultTrajectoryHistoryLimit = 30
	maxTrajectoryHistoryLimit     = 200
)

// TrajectoryHandler streams one agent session's trajectory events via SSE.
type TrajectoryHandler struct {
	store TrajectoryStreamStore
}

// NewTrajectoryHandler creates a handler that streams trajectory events
// for a running agent session as Server-Sent Events.
func NewTrajectoryHandler(store TrajectoryStreamStore) gin.HandlerFunc {
	return (&TrajectoryHandler{store: store}).Handle
}

// NewTrajectoryHistoryHandler creates a handler that returns recent trajectory
// items ordered newest-first. It is additive and keeps the SSE endpoint's
// default replay behaviour unchanged.
func NewTrajectoryHistoryHandler(store TrajectoryHistoryStore) gin.HandlerFunc {
	return (&TrajectoryHistoryHandler{store: store}).Handle
}

// NewTrajectoryLiveHandler creates an all-agent live trajectory SSE handler.
func NewTrajectoryLiveHandler(store TrajectoryLiveStore) gin.HandlerFunc {
	return (&TrajectoryLiveHandler{store: store}).Handle
}

// NewTrajectoryLatestHandler creates a handler returning each active agent's latest trajectory item.
func NewTrajectoryLatestHandler(store TrajectoryLatestStore) gin.HandlerFunc {
	return (&TrajectoryLatestHandler{store: store}).Handle
}

// Handle streams trajectory events as Server-Sent Events for the given agent.
func (h *TrajectoryHandler) Handle(c *gin.Context) {
	agentID := c.Param("agentId")
	if agentID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
		return
	}

	// Verify the agent has an active session.
	if _, ok := h.store.Session(agentID); !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "no active session for agent"})
		return
	}

	var events <-chan types.TrajectoryLogItem
	if c.Query("replay") == "false" {
		events = h.store.SubscribeLiveTrajectory(agentID)
	} else {
		events = h.store.SubscribeTrajectory(agentID)
	}
	defer h.store.UnsubscribeTrajectory(agentID, events)

	c.Writer.Header().Set("Content-Type", "text/event-stream")
	c.Writer.Header().Set("Cache-Control", "no-cache")
	c.Writer.Header().Set("Connection", "keep-alive")
	c.Writer.Header().Set("X-Accel-Buffering", "no")
	c.Writer.Flush()

	clientGone := c.Request.Context().Done()

	for {
		select {
		case <-clientGone:
			return
		case item, ok := <-events:
			if !ok {
				// Channel closed (agent session ended).
				fmt.Fprintf(c.Writer, "event: done\ndata: {}\n\n")
				c.Writer.Flush()
				return
			}
			data, err := json.Marshal(item)
			if err != nil {
				continue
			}
			fmt.Fprintf(c.Writer, "event: trajectory\ndata: %s\n\n", data)
			c.Writer.Flush()
		}
	}
}

// TrajectoryHistoryHandler returns bounded trajectory history.
type TrajectoryHistoryHandler struct {
	store TrajectoryHistoryStore
}

// Handle returns a bounded page of trajectory history for the given agent.
func (h *TrajectoryHistoryHandler) Handle(c *gin.Context) {
	agentID := c.Param("agentId")
	if agentID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
		return
	}

	limit := defaultTrajectoryHistoryLimit
	if raw := c.Query("limit"); raw != "" {
		parsed, err := strconv.Atoi(raw)
		if err != nil || parsed < 1 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "limit must be a positive integer"})
			return
		}
		limit = parsed
	}
	if limit > maxTrajectoryHistoryLimit {
		limit = maxTrajectoryHistoryLimit
	}

	var before int64
	if raw := c.Query("before"); raw != "" {
		parsed, err := strconv.ParseInt(raw, 10, 64)
		if err != nil || parsed < 1 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "before must be a positive unix millisecond timestamp"})
			return
		}
		before = parsed
	}

	c.JSON(http.StatusOK, h.store.TrajectoryHistory(agentID, limit, before))
}

// TrajectoryLiveHandler streams new trajectory events for all active agents.
type TrajectoryLiveHandler struct {
	store TrajectoryLiveStore
}

// Handle streams new trajectory events for all active agents.
func (h *TrajectoryLiveHandler) Handle(c *gin.Context) {
	events := h.store.SubscribeAllLiveTrajectory()
	defer h.store.UnsubscribeAllTrajectory(events)

	c.Writer.Header().Set("Content-Type", "text/event-stream")
	c.Writer.Header().Set("Cache-Control", "no-cache")
	c.Writer.Header().Set("Connection", "keep-alive")
	c.Writer.Header().Set("X-Accel-Buffering", "no")
	c.Writer.Flush()

	clientGone := c.Request.Context().Done()
	for {
		select {
		case <-clientGone:
			return
		case item, ok := <-events:
			if !ok {
				fmt.Fprintf(c.Writer, "event: done\ndata: {}\n\n")
				c.Writer.Flush()
				return
			}
			data, err := json.Marshal(item)
			if err != nil {
				continue
			}
			fmt.Fprintf(c.Writer, "event: trajectory\ndata: %s\n\n", data)
			c.Writer.Flush()
		}
	}
}

// TrajectoryLatestHandler returns the latest trajectory item for active sessions.
type TrajectoryLatestHandler struct {
	store TrajectoryLatestStore
}

// Handle returns the latest trajectory item for each active agent session.
func (h *TrajectoryLatestHandler) Handle(c *gin.Context) {
	sessions := h.store.Sessions()
	agentIDs := make([]string, 0, len(sessions))
	seen := make(map[string]struct{}, len(sessions))
	for _, snapshot := range sessions {
		for _, agentID := range []string{snapshot.AgentConfigID, snapshot.AgentID} {
			if agentID == "" {
				continue
			}
			if _, ok := seen[agentID]; ok {
				continue
			}
			seen[agentID] = struct{}{}
			agentIDs = append(agentIDs, agentID)
		}
	}
	c.JSON(http.StatusOK, h.store.LatestTrajectoryByAgent(agentIDs))
}
