package dispatcher

import (
	"fmt"
	"log/slog"
	"reflect"
	"strings"

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

// RefreshRunningAgentConfig updates live sessions after the persistent agent
// catalog changes. Session snapshots are derived from the in-memory session
// copy, so keeping that copy current ensures dashboard and API consumers
// all observe the same identity as agents.yaml.
func (d *Dispatcher) RefreshRunningAgentConfig(updated agent.Config) bool {
	if d == nil {
		return false
	}
	agentID := strings.TrimSpace(updated.ID)
	if agentID == "" {
		return false
	}

	changedAgentIDs := make(map[string]struct{})
	for _, sess := range d.sessionManager.Each(func(sess *interactiveSession) bool {
		sess.mu.Lock()
		defer sess.mu.Unlock()
		return strings.TrimSpace(sess.agent.ID) == agentID ||
			strings.TrimSpace(sess.agentID) == agentID ||
			strings.TrimSpace(sess.serverAgentID) == agentID
	}) {
		if sess == nil {
			continue
		}
		sess.mu.Lock()
		changed := refreshSessionAgentConfigLocked(sess, updated)
		sessionAgentID := strings.TrimSpace(sess.agentID)
		if sessionAgentID == "" {
			sessionAgentID = agentID
		}
		sess.mu.Unlock()
		if changed {
			changedAgentIDs[sessionAgentID] = struct{}{}
		}
	}

	for changedAgentID := range changedAgentIDs {
		d.publishSessionStateChanged(changedAgentID)
	}
	return len(changedAgentIDs) > 0
}

// RefreshRunningAgentStartConfig applies a fresh server agent:start config to
// an already-running AgentTeam-owned session before routing the wake as delivery.
// This keeps the live runtime guard on the same server-provided execution
// policy used by cold starts.
func (d *Dispatcher) RefreshRunningAgentStartConfig(serverAgentID string, cfg types.AgentStartConfig) bool {
	if d == nil {
		return false
	}
	agentID := strings.TrimSpace(serverAgentID)
	if agentID == "" {
		return false
	}
	updated, rt, err := agentConfigFromAgentTeamStartConfig(agentID, cfg)
	if err != nil {
		slogWarnRefreshRunningAgentStartConfig(agentID, err)
		return false
	}

	changedAgentIDs := make(map[string]struct{})
	restartedAgentIDs := make(map[string]struct{})
	for _, sess := range d.sessionManager.Each(func(sess *interactiveSession) bool {
		sess.mu.Lock()
		defer sess.mu.Unlock()
		return strings.TrimSpace(sess.agent.ID) == agentID ||
			strings.TrimSpace(sess.agentID) == agentID ||
			strings.TrimSpace(sess.serverAgentID) == agentID
	}) {
		if sess == nil {
			continue
		}

		sessionAgentID := runningSessionAgentID(sess, agentID)
		if restarted := d.restartSessionForAgentTeamStartConfig(sess, sessionAgentID, updated, rt); restarted {
			changedAgentIDs[sessionAgentID] = struct{}{}
			restartedAgentIDs[sessionAgentID] = struct{}{}
			continue
		}

		sess.mu.Lock()
		changed := refreshSessionAgentConfigLocked(sess, updated)
		sess.mu.Unlock()
		if changed {
			changedAgentIDs[sessionAgentID] = struct{}{}
		}
	}

	for changedAgentID := range changedAgentIDs {
		if _, restarted := restartedAgentIDs[changedAgentID]; restarted {
			continue
		}
		d.publishSessionStateChanged(changedAgentID)
	}
	return len(changedAgentIDs) > 0
}

func agentConfigFromAgentTeamStartConfig(agentID string, cfg types.AgentStartConfig) (agent.Config, runtimeconfig.Runtime, error) {
	mgr := profile.Default()
	base, ok := mgr.Agents().Get(agentID)
	if !ok {
		return agent.Config{}, runtimeconfig.Runtime{}, fmt.Errorf("agent %q not found in local config", agentID)
	}
	updated := applyAgentTeamStartConfig(base, cfg)
	rt, ok := resolveConfiguredRuntime(mgr, updated.Runtime)
	if !ok {
		return agent.Config{}, runtimeconfig.Runtime{}, fmt.Errorf("runtime %q not found for agent %q", updated.Runtime, agentID)
	}
	return updated, rt, nil
}

func slogWarnRefreshRunningAgentStartConfig(agentID string, err error) {
	slog.Warn("agentteam start: refresh running agent config failed",
		"server_agent_id", agentID, "error", err)
}

func runningSessionAgentID(sess *interactiveSession, fallback string) string {
	if sess == nil {
		return strings.TrimSpace(fallback)
	}
	sess.mu.Lock()
	defer sess.mu.Unlock()
	sessionAgentID := strings.TrimSpace(sess.agentID)
	if sessionAgentID == "" {
		sessionAgentID = strings.TrimSpace(fallback)
	}
	return sessionAgentID
}

func (d *Dispatcher) restartSessionForAgentTeamStartConfig(sess *interactiveSession, sessionAgentID string, updated agent.Config, rt runtimeconfig.Runtime) bool {
	if d == nil || sess == nil {
		return false
	}
	sess.mu.Lock()
	process := sess.process
	sess.mu.Unlock()
	if process == nil {
		return false
	}

	restarted, err := d.restartSessionIfRuntimeStartConfigChanged(sess, updated, rt, conversationBinding{})
	if err == nil {
		return restarted
	}

	slog.Warn("agentteam start: runtime start guard failed; stopping live session before wake routing",
		"session_agent_id", sessionAgentID,
		"server_agent_id", strings.TrimSpace(updated.ID),
		"runtime", strings.TrimSpace(updated.Runtime),
		"error", err)
	return d.interruptSessionForRuntimeStartReplacement(sess, updated, rt, "")
}

func refreshSessionAgentConfigLocked(sess *interactiveSession, updated agent.Config) bool {
	current := sess.agent
	if updated.ID != "" {
		current.ID = updated.ID
	}
	if updated.Name != "" {
		current.Name = updated.Name
	}
	if updated.Description != "" {
		current.Description = updated.Description
	}
	if updated.Runtime != "" {
		current.Runtime = updated.Runtime
	}
	if updated.PermissionMode != "" {
		current.PermissionMode = updated.PermissionMode
	}
	if !runtimePolicySnapshotIsZero(updated.RuntimePolicy) {
		current.RuntimePolicy = updated.RuntimePolicy
		current.RuntimeDebugEnabled = updated.RuntimeDebugEnabled
	} else if !updated.Source.IsZero() {
		current.RuntimeDebugEnabled = updated.RuntimeDebugEnabled
	}
	if updated.LLMMode != "" {
		current.LLMMode = updated.LLMMode
	}
	if hasAuthoritativeLLMConfigUpdate(updated) {
		current.BaseURL = updated.BaseURL
		current.APIKey = updated.APIKey
		current.Headers = updated.Headers
		current.WireAPI = updated.WireAPI
	} else {
		if updated.BaseURL != "" {
			current.BaseURL = updated.BaseURL
		}
		if updated.APIKey != "" {
			current.APIKey = updated.APIKey
		}
		if updated.Headers != nil {
			current.Headers = updated.Headers
		}
		if updated.WireAPI != "" {
			current.WireAPI = updated.WireAPI
		}
	}
	if updated.Model != "" {
		current.Model = updated.Model
	}
	if updated.ReasoningEffort != "" {
		current.ReasoningEffort = updated.ReasoningEffort
	}
	if !updated.Source.IsZero() {
		current.Source = updated.Source
	}
	if reflect.DeepEqual(current, sess.agent) {
		return false
	}
	sess.agent = current
	return true
}

func hasAuthoritativeLLMConfigUpdate(updated agent.Config) bool {
	return updated.LLMMode != "" ||
		updated.BaseURL != "" ||
		updated.APIKey != "" ||
		updated.Headers != nil ||
		updated.WireAPI != ""
}
