package profile

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

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
	"github.com/tidbcloud/AgentTeam/desktop/internal/crypto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/provider"
)

// AgentLLMResolution is the daemon's normalized view of one agent's LLM
// configuration after applying runtime-default fallback rules.
type AgentLLMResolution struct {
	Mode            agent.LLMMode
	Upstream        *provider.Provider
	Model           string
	ReasoningEffort string
}

// RuntimeLLMResolution is the daemon's normalized view of one runtime's LLM
// defaults after applying an optional runtime-scoped model selection.
type RuntimeLLMResolution struct {
	Upstream        *provider.Provider
	Model           string
	ReasoningEffort string
}

// ResolveRuntimeLLM resolves effective LLM settings for an Agent-free runtime
// launch. External runtimes retain their native provider configuration, while
// builtin must receive AgentTeam's normalized provider explicitly because its
// isolated config does not load the user's native OpenCode config.
func ResolveRuntimeLLM(runtimeType runtimeconfig.RuntimeType, selectedModel, selectedReasoningEffort string) (RuntimeLLMResolution, error) {
	result, _, err := resolveRuntimeLLM(runtimeType, selectedModel, selectedReasoningEffort)
	return result, err
}

func resolveRuntimeLLM(runtimeType runtimeconfig.RuntimeType, selectedModel, selectedReasoningEffort string) (RuntimeLLMResolution, RuntimeConfigState, error) {
	requestedType := runtimeconfig.NormalizeType(strings.TrimSpace(string(runtimeType)))
	if requestedType == "" {
		return RuntimeLLMResolution{}, RuntimeConfigState{}, fmt.Errorf("resolve runtime llm: runtime type is required")
	}
	if _, err := runtimeconfig.ValidateType(string(requestedType)); err != nil {
		return RuntimeLLMResolution{}, RuntimeConfigState{}, fmt.Errorf("resolve runtime llm: %w", err)
	}
	normalizedType := runtimeconfig.EffectiveRuntimeType(requestedType)

	state, err := ReadRuntimeConfig(string(normalizedType))
	if err != nil {
		return RuntimeLLMResolution{}, RuntimeConfigState{}, err
	}
	result := RuntimeLLMResolution{
		Model:           strings.TrimSpace(state.Editable.Model),
		ReasoningEffort: strings.TrimSpace(state.Editable.ReasoningEffort),
	}
	if model := normalizeRuntimeModelSelection(requestedType, strings.TrimSpace(selectedModel), result.Model); model != "" {
		result.Model = model
	}
	if reasoningEffort := strings.TrimSpace(selectedReasoningEffort); reasoningEffort != "" {
		result.ReasoningEffort = reasoningEffort
	}

	if shouldInjectRuntimeDefaultProvider(requestedType) {
		runtimeUpstream := buildRuntimeProviderForValidation(string(normalizedType), state.Editable)
		if strings.TrimSpace(runtimeUpstream.BaseURL) != "" {
			resolvedRuntimeUpstream := resolveProviderEnvKeyForRuntime(provider.Normalize(runtimeUpstream))
			if resolvedRuntimeUpstream.BaseURL == "" {
				resolvedRuntimeUpstream.BaseURL = resolvedRuntimeUpstream.ResolvedBaseURL()
			}
			result.Upstream = &resolvedRuntimeUpstream
		}
	}
	return result, state, nil
}

// ResolveAgentLLM resolves an agent's effective LLM configuration for
// runtimes that connect directly to the selected upstream provider.
func ResolveAgentLLM(ag agent.Config, runtimeType runtimeconfig.RuntimeType) (AgentLLMResolution, error) {
	requestedType := runtimeconfig.NormalizeType(strings.TrimSpace(string(runtimeType)))
	if requestedType == "" {
		return AgentLLMResolution{}, fmt.Errorf("resolve agent llm: runtime type is required")
	}
	normalizedType := runtimeconfig.EffectiveRuntimeType(requestedType)

	_, baseURL, apiKey, headers, wireAPI := agent.NormalizeAgentLLMFields(ag.LLMMode, ag.BaseURL, ag.APIKey, ag.Headers, ag.WireAPI)
	apiKey = decryptAgentAPIKeyIfNeeded(apiKey)
	runtimeResolution, state, err := resolveRuntimeLLM(requestedType, ag.Model, ag.ReasoningEffort)
	if err != nil {
		return AgentLLMResolution{}, err
	}

	result := AgentLLMResolution{
		Mode:            agent.LLMModeRuntimeDefault,
		Upstream:        runtimeResolution.Upstream,
		Model:           runtimeResolution.Model,
		ReasoningEffort: runtimeResolution.ReasoningEffort,
	}

	if !hasExplicitAgentLLMUpstream(baseURL, apiKey, headers, wireAPI) {
		return result, nil
	}

	merged := mergeRuntimeConfigEditable(string(normalizedType), state.Editable, RuntimeConfigEditable{
		BaseURL:          baseURL,
		APIKey:           apiKey,
		APIKeyConfigured: strings.TrimSpace(apiKey) != "",
		WireAPI:          wireAPI,
	})
	result.Mode = agent.LLMModeInline

	name := strings.TrimSpace(ag.Name)
	if name == "" {
		name = strings.TrimSpace(ag.ID)
	}
	if name == "" {
		name = string(normalizedType)
	}

	wireAPI = strings.TrimSpace(wireAPI)
	if wireAPI == "" {
		wireAPI = strings.TrimSpace(merged.WireAPI)
	}
	if wireAPI == "" {
		wireAPI = defaultWireAPIForRuntime(normalizedType)
	}

	upstream := resolveProviderEnvKeyForRuntime(provider.Normalize(provider.Provider{
		ID:       agentLLMProviderID(ag.ID, normalizedType),
		Name:     name,
		Source:   string(normalizedType),
		BaseURL:  merged.BaseURL,
		APIKey:   merged.APIKey,
		Headers:  headers,
		WireAPIs: []string{wireAPI},
		EnvKey:   merged.APIKeyEnvKey,
	}))
	if upstream.BaseURL == "" {
		upstream.BaseURL = upstream.ResolvedBaseURL()
	}

	return AgentLLMResolution{
		Mode:            result.Mode,
		Upstream:        &upstream,
		Model:           result.Model,
		ReasoningEffort: result.ReasoningEffort,
	}, nil
}

func hasExplicitAgentLLMUpstream(baseURL string, apiKey string, headers map[string]string, wireAPI string) bool {
	return strings.TrimSpace(baseURL) != "" ||
		strings.TrimSpace(apiKey) != "" ||
		strings.TrimSpace(wireAPI) != "" ||
		len(headers) > 0
}

func shouldInjectRuntimeDefaultProvider(runtimeType runtimeconfig.RuntimeType) bool {
	// External runtimes own their native default provider config. The builtin
	// runtime has no separate native config, so AgentTeam must supply its provider.
	return runtimeType == runtimeconfig.TypeBuiltin
}

func normalizeRuntimeModelSelection(runtimeType runtimeconfig.RuntimeType, selectedModel, defaultModel string) string {
	selectedModel = strings.TrimSpace(selectedModel)
	if selectedModel == "" || runtimeType != runtimeconfig.TypeBuiltin {
		return selectedModel
	}

	// Machine readiness advertises OpenCode's native provider/model value (for
	// example "anthropic/model"), while builtin replaces that native provider
	// with AgentTeam's injected provider. Strip only the provider prefix proven by
	// the current native default; model IDs may themselves contain slashes.
	nativeDefault := strings.TrimSpace(detectRuntimeDefault(string(runtimeType)).Model)
	defaultModel = strings.TrimSpace(defaultModel)
	if nativeDefault == "" || defaultModel == "" {
		return selectedModel
	}
	suffix := "/" + defaultModel
	if !strings.HasSuffix(nativeDefault, suffix) {
		return selectedModel
	}
	providerPrefix := strings.TrimSuffix(nativeDefault, suffix)
	if providerPrefix == "" {
		return selectedModel
	}
	return strings.TrimPrefix(selectedModel, providerPrefix+"/")
}

func decryptAgentAPIKeyIfNeeded(apiKey string) string {
	trimmed := strings.TrimSpace(apiKey)
	if trimmed == "" || !crypto.KeysSet() || !crypto.IsEncrypted(trimmed) {
		return trimmed
	}
	decrypted, err := crypto.Decrypt(trimmed)
	if err != nil {
		slog.Warn("resolve agent llm: decrypt local agent api key", "error", err)
		return trimmed
	}
	return strings.TrimSpace(decrypted)
}

func defaultWireAPIForRuntime(runtimeType runtimeconfig.RuntimeType) string {
	switch runtimeType {
	case runtimeconfig.TypeClaudeCode:
		return provider.WireAPIAnthropic
	case runtimeconfig.TypeCodex:
		return provider.WireAPIResponses
	default:
		return provider.WireAIOpenAI
	}
}

func agentLLMProviderID(agentID string, runtimeType runtimeconfig.RuntimeType) string {
	trimmedAgentID := strings.TrimSpace(agentID)
	if trimmedAgentID != "" {
		return trimmedAgentID
	}
	return strings.TrimSpace(string(runtimeType)) + "-llm"
}
