package dashboard

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agentview"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

// ---------------------------------------------------------------------------
// GET /agents
// ---------------------------------------------------------------------------

// ListAgentsHandler handles listing all synced agents.
type ListAgentsHandler struct {
	sessions agentSessionStore
}

type agentSessionStore interface {
	Sessions() []types.SessionSnapshot
}

// NewListAgentsHandler creates a new ListAgentsHandler.
func NewListAgentsHandler(sessions ...agentSessionStore) gin.HandlerFunc {
	return (&ListAgentsHandler{sessions: firstAgentSessionStore(sessions)}).Handle
}

// Handle returns all agent definitions.
func (h *ListAgentsHandler) Handle(c *gin.Context) {
	mgr := profile.Default()
	agents := mgr.Agents().All()
	sessions := agentSessionsByID(agentSessionSnapshots(h.sessions))
	resp := make([]agentview.Snapshot, 0, len(agents))
	for _, ag := range agents {
		snapshot, ok := sessions[ag.ID]
		resp = append(resp, BuildAgentResponseWithSession(ag, snapshot, ok))
	}
	c.JSON(http.StatusOK, resp)
}

// ---------------------------------------------------------------------------
// GET /agents/:id
// ---------------------------------------------------------------------------

// GetAgentHandler handles retrieving a single agent by ID.
type GetAgentHandler struct {
	sessions agentSessionStore
}

// NewGetAgentHandler creates a new GetAgentHandler.
func NewGetAgentHandler(sessions ...agentSessionStore) gin.HandlerFunc {
	return (&GetAgentHandler{sessions: firstAgentSessionStore(sessions)}).Handle
}

// Handle returns a single agent definition.
func (h *GetAgentHandler) Handle(c *gin.Context) {
	mgr := profile.Default()
	id := c.Param("id")
	ag, ok := mgr.Agents().Get(id)
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("agent %q not found", id)})
		return
	}
	snapshot, hasSession := agentSessionsByID(agentSessionSnapshots(h.sessions))[ag.ID]
	c.JSON(http.StatusOK, BuildAgentResponseWithSession(ag, snapshot, hasSession))
}

// ---------------------------------------------------------------------------
// Shared
// ---------------------------------------------------------------------------

// BuildAgentResponse resolves composition state and builds the canonical Agent snapshot.
func BuildAgentResponse(ag agent.Config) agentview.Snapshot {
	return agentview.Build(ag, buildAgentContext(ag))
}

func buildAgentContext(ag agent.Config) agentview.BuildContext {
	context := agentview.BuildContext{}
	// Resolve source name from channel ID when available.
	if ag.Source.ID != "" {
		if ch, ok := profile.Default().Servers().Get(ag.Source.ID); ok {
			context.SourceName = ch.Name
		}
	}

	if rt, ok := profile.Default().Runtimes().Get(ag.Runtime); ok {
		configuredType := runtimeconfig.NormalizeType(string(rt.Type))
		context.RuntimeType = string(configuredType)
	} else if rt, err := runtimeconfig.ValidateType(ag.Runtime); err == nil {
		configuredType := runtimeconfig.NormalizeType(string(rt))
		context.RuntimeType = string(configuredType)
	}
	return context
}

// BuildAgentResponseWithSession converts an agent config and overlays live
// local session state already held by the daemon dashboard runtime.
func BuildAgentResponseWithSession(ag agent.Config, snapshot types.SessionSnapshot, hasSession bool) agentview.Snapshot {
	context := buildAgentContext(ag)
	if hasSession {
		context.Session = &snapshot
	}
	return agentview.Build(ag, context)
}

func firstAgentSessionStore(stores []agentSessionStore) agentSessionStore {
	if len(stores) == 0 {
		return nil
	}
	return stores[0]
}

func agentSessionSnapshots(store agentSessionStore) []types.SessionSnapshot {
	if store == nil {
		return nil
	}
	return store.Sessions()
}

func agentSessionsByID(snapshots []types.SessionSnapshot) map[string]types.SessionSnapshot {
	sessions := make(map[string]types.SessionSnapshot, len(snapshots)*2)
	for _, snapshot := range snapshots {
		if id := strings.TrimSpace(snapshot.AgentID); id != "" {
			sessions[id] = snapshot
		}
		if id := strings.TrimSpace(snapshot.AgentConfigID); id != "" {
			sessions[id] = snapshot
		}
		if id := strings.TrimSpace(snapshot.ServerAgentID); id != "" {
			sessions[id] = snapshot
		}
	}
	return sessions
}
