package dashboard

import (
	"sort"
	"strings"

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

const (
	runtimeStatusMissing     = "missing"
	runtimeStatusUnsupported = "unsupported"
	runtimeStatusReady       = "ready"
)

type dashboardRuntimeState struct {
	runtimes []dto.RuntimeBundleItem
	ready    map[string]bool
	missing  map[string]bool
}

func buildDashboardRuntimes(
	runtimes []runtimeconfig.Runtime,
	detected []runtimeconfig.DetectResult,
	agents []agent.Config,
	summary *dto.RuntimeBundleSummary,
) dashboardRuntimeState {
	detectedByType := make(map[string]dto.DetectRuntimeResponse, len(detected))
	for _, d := range detected {
		detectedByType[string(d.Type)] = toDetectRuntimeResponse(d)
	}

	agentCounts := make(map[string]int)
	for _, ag := range agents {
		runtimeName := dashboardRuntimeKey(ag.Runtime)
		if runtimeName == "" {
			continue
		}
		agentCounts[runtimeName]++
	}

	state := dashboardRuntimeState{
		runtimes: make([]dto.RuntimeBundleItem, 0, len(runtimes)+len(agentCounts)),
		ready:    make(map[string]bool),
		missing:  make(map[string]bool),
	}
	seen := make(map[string]struct{}, len(runtimes))

	for _, rt := range runtimes {
		runtimeResp := ToRuntimeResponse(rt)
		item := dashboardRuntimeFromResponse(
			runtimeResp,
			detectedByType,
			agentCounts[rt.Name],
			runtimeConfigOfflineOK(runtimeResp.Type),
		)
		state.add(item, summary)
		seen[rt.Name] = struct{}{}
	}

	var inferred []string
	for runtimeName := range agentCounts {
		if _, ok := seen[runtimeName]; ok {
			continue
		}
		inferred = append(inferred, runtimeName)
	}
	sort.Strings(inferred)
	for _, runtimeName := range inferred {
		runtimeType := dashboardRuntimeKey(runtimeName)
		runtimeResp := dto.RuntimeResponse{
			Name:   runtimeType,
			Type:   runtimeType,
			Binary: runtimeconfig.DefaultBinary(runtimeconfig.RuntimeType(runtimeType)),
		}
		item := dashboardRuntimeFromResponse(
			runtimeResp,
			detectedByType,
			agentCounts[runtimeName],
			runtimeConfigOfflineOK(runtimeResp.Type),
		)
		if item.Attention == "" {
			item.Attention = "runtime not detected"
		}
		state.add(item, summary)
	}

	for _, rt := range runtimeconfig.AllRuntimeTypes {
		if _, ok := seen[string(rt)]; ok {
			continue
		}
		if _, ok := agentCounts[string(rt)]; ok {
			continue
		}
		runtimeResp := dto.RuntimeResponse{
			Name:   string(rt),
			Type:   string(rt),
			Binary: runtimeconfig.DefaultBinary(rt),
		}
		item := dashboardRuntimeFromResponse(runtimeResp, detectedByType, 0, runtimeConfigOfflineOK(runtimeResp.Type))
		if item.Attention == "" {
			item.Attention = "runtime not detected"
		}
		state.add(item, summary)
	}

	return state
}

func dashboardRuntimeFromResponse(
	runtimeResp dto.RuntimeResponse,
	detectedByType map[string]dto.DetectRuntimeResponse,
	agentCount int,
	configured bool,
) dto.RuntimeBundleItem {
	item := dto.RuntimeBundleItem{Runtime: runtimeResp, AgentCount: agentCount}
	if detect, ok := detectedByType[runtimeResp.Type]; ok {
		detected := detect
		item.Detected = &detected
		item.Installed = detected.Installed
		item.Capable = detected.Capable
		item.ReasonCode = detected.ReasonCode
		item.Message = detected.Message
	}
	if item.Detected == nil {
		item.ReasonCode = runtimeconfig.RuntimeProbeReasonBinaryMissing
		item.Message = "runtime binary was not detected"
	}

	effectiveType := runtimeconfig.EffectiveRuntimeType(runtimeconfig.NormalizeType(strings.TrimSpace(runtimeResp.Type)))
	item.Configured = configured || effectiveType == runtimeconfig.TypeCodex

	if !item.Installed {
		item.Status = runtimeStatusMissing
		item.Attention = "runtime not installed"
		return item
	}
	if !item.Capable {
		item.Status = runtimeStatusUnsupported
		if item.Message == "" {
			item.Message = "runtime binary is installed but does not meet AgentTeam capability requirements"
		}
		item.Attention = item.Message
		return item
	}

	item.Ready = true
	item.Status = runtimeStatusReady
	return item
}

func dashboardRuntimeKey(runtimeName string) string {
	rt, err := runtimeconfig.ValidateType(strings.TrimSpace(runtimeName))
	if err != nil {
		return strings.TrimSpace(runtimeName)
	}
	return string(runtimeconfig.NormalizeType(string(rt)))
}

func runtimeConfigOfflineOK(runtimeType string) bool {
	state, err := profile.ReadRuntimeConfig(runtimeType)
	return err == nil && state.Offline.OK
}

func (s *dashboardRuntimeState) add(item dto.RuntimeBundleItem, summary *dto.RuntimeBundleSummary) {
	s.runtimes = append(s.runtimes, item)
	summary.RuntimeTotal++
	key := item.Runtime.Name
	if key == "" {
		key = item.Runtime.Type
	}
	s.ready[key] = item.Ready
	if item.Installed {
		summary.RuntimeInstalled++
	}
	if item.Ready {
		summary.RuntimeReady++
		return
	}
	s.missing[key] = true
	summary.RuntimeMissing++
}

func buildDashboardAgents(
	agents []agent.Config,
	sessions []types.SessionSnapshot,
	runtimeState dashboardRuntimeState,
	summary *dto.RuntimeBundleSummary,
) []dto.RuntimeAgentItem {
	sessionsByAgent := agentSessionsByID(sessions)

	result := make([]dto.RuntimeAgentItem, 0, len(agents))
	for _, ag := range agents {
		snapshot, hasSession := sessionsByAgent[ag.ID]
		agentResp := BuildAgentResponseWithSession(ag, snapshot, hasSession)
		item := dto.RuntimeAgentItem{Agent: agentResp}
		if hasSession {
			item.Active = isActiveSession(snapshot)
			item.Working = item.Active && isWorkingSession(snapshot)
			item.DeliveryStatus = strings.TrimSpace(snapshot.DeliveryStatus)
			item.Activity = strings.TrimSpace(snapshot.Activity)
			item.ActivityInfo = strings.TrimSpace(snapshot.ActivityDetail)
			item.ServerID = strings.TrimSpace(snapshot.ChannelID)
			item.MessageCount = snapshot.MessageCount
			item.WorkDir = strings.TrimSpace(snapshot.WorkDir)
		}
		if runtimeState.missing[dashboardRuntimeKey(ag.Runtime)] {
			item.Attention = "runtime not ready"
		}

		summary.AgentTotal++
		if item.Active {
			summary.AgentRunning++
		}
		if item.Working {
			summary.AgentWorking++
		}
		result = append(result, item)
	}
	return result
}

func isActiveSession(snapshot types.SessionSnapshot) bool {
	status := strings.ToLower(strings.TrimSpace(snapshot.Status))
	return status != "" && status != "stopped" && status != "inactive"
}

func isWorkingSession(snapshot types.SessionSnapshot) bool {
	status := strings.ToLower(strings.TrimSpace(snapshot.Status))
	activity := strings.ToLower(strings.TrimSpace(snapshot.Activity))
	deliveryStatus := strings.ToLower(strings.TrimSpace(snapshot.DeliveryStatus))
	switch deliveryStatus {
	case "active":
		return true
	case "idle", "stopped":
		return false
	}
	if strings.Contains(status, "working") || strings.Contains(status, "busy") || strings.Contains(status, "progress") {
		return true
	}
	return activity != "" && activity != "idle" && activity != "sleeping"
}
