package agentteambridge

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

	"github.com/tidbcloud/AgentTeam/internal/apr/provider/codex"
	"github.com/tidbcloud/AgentTeam/internal/apr/provider/copilot"
	"github.com/tidbcloud/AgentTeam/internal/apr/runtime"
	"github.com/tidbcloud/AgentTeam/internal/apr/types"
	"github.com/tidbcloud/AgentTeam/internal/store/organization"
)

type OrganizationProviderLoader interface {
	ListActiveOrganizationProvidersForLLM(ctx context.Context) ([]organization.OrganizationProviderView, error)
	ListActiveOrganizationProviderRoutesForLLM(ctx context.Context) ([]organization.OrganizationProviderRouteView, error)
	ListActiveOrganizationProviderRouteGroupsForLLM(ctx context.Context) ([]organization.OrganizationProviderRouteGroupView, error)
	LoadActiveOrganizationProviderTokens(ctx context.Context, organizationID string, providerID string) ([]organization.OrganizationProviderTokenSecret, error)
	UpdateOrganizationProviderTokenSecret(ctx context.Context, organizationID string, providerID string, tokenID string, params organization.UpdateOrganizationProviderTokenSecretParams) error
}

type LoadResult struct {
	ProviderCount int
	RouteCount    int
	TokenCount    int
	SkippedCount  int
}

type builtProvider struct {
	provider   types.LLMProvider
	cleanup    func()
	codex      *codex.Codex
	runtimeKey string
}

type ProviderHTTPClients struct {
	Upstream *http.Client
	Setup    *http.Client
}

func (clients ProviderHTTPClients) normalized() ProviderHTTPClients {
	if clients.Setup == nil {
		clients.Setup = clients.Upstream
	}
	return clients
}

func ApplyOrganizationProviders(ctx context.Context, dispatcher *runtime.Dispatcher, loader OrganizationProviderLoader, httpClient *http.Client, transporter types.HTTPProxy) (LoadResult, []func(), map[string]*codex.Codex, error) {
	return ApplyOrganizationProvidersWithHTTPClients(ctx, dispatcher, loader, ProviderHTTPClients{Upstream: httpClient, Setup: httpClient}, transporter)
}

func ApplyOrganizationProvidersWithHTTPClients(ctx context.Context, dispatcher *runtime.Dispatcher, loader OrganizationProviderLoader, clients ProviderHTTPClients, transporter types.HTTPProxy) (LoadResult, []func(), map[string]*codex.Codex, error) {
	return applyOrganizationProvidersWithHTTPClients(ctx, ctx, dispatcher, loader, clients, transporter)
}

func applyOrganizationProvidersWithHTTPClients(ctx context.Context, providerLifecycle context.Context, dispatcher *runtime.Dispatcher, loader OrganizationProviderLoader, clients ProviderHTTPClients, transporter types.HTTPProxy) (LoadResult, []func(), map[string]*codex.Codex, error) {
	if dispatcher == nil {
		return LoadResult{}, nil, nil, fmt.Errorf("apr dispatcher is nil")
	}
	if loader == nil {
		return LoadResult{}, nil, nil, fmt.Errorf("organization provider loader is nil")
	}
	clients = clients.normalized()

	providers, err := loader.ListActiveOrganizationProvidersForLLM(ctx)
	if err != nil {
		return LoadResult{}, nil, nil, fmt.Errorf("list active organization providers: %w", err)
	}
	routeViews, err := loader.ListActiveOrganizationProviderRoutesForLLM(ctx)
	if err != nil {
		return LoadResult{}, nil, nil, fmt.Errorf("list active organization provider routes: %w", err)
	}
	groupViews, err := loader.ListActiveOrganizationProviderRouteGroupsForLLM(ctx)
	if err != nil {
		return LoadResult{}, nil, nil, fmt.Errorf("list active organization provider route groups: %w", err)
	}

	result := LoadResult{}
	modelsByProvider := providerModelsFromRouteGroups(groupViews)
	providerMap := make(map[string]organization.OrganizationProviderView, len(providers))
	loadedProviders := make([]builtProvider, 0, len(providers))
	cleanups := make([]func(), 0, len(providers))
	codexProviders := make(map[string]*codex.Codex)

	for _, providerView := range providers {
		driverKind := strings.TrimSpace(providerView.DriverKind)
		baseURL := firstNonEmpty(providerView.BaseURL, providerView.Endpoints.MessagesBaseURL, providerView.Endpoints.ResponsesBaseURL, providerView.Endpoints.EmbeddingsBaseURL)
		if !providerCanUseImplicitBaseURL(driverKind) && baseURL == "" {
			result.SkippedCount++
			fmt.Printf("APR registry skip provider=%s driver=%s reason=missing_base_url\n", strings.TrimSpace(providerView.ID), driverKind)
			continue
		}

		tokens, err := loader.LoadActiveOrganizationProviderTokens(ctx, providerView.OrganizationID, providerView.ID)
		if err != nil {
			cleanupFunctions(cleanups)
			return LoadResult{}, nil, nil, fmt.Errorf("load active tokens for provider %s: %w", providerView.ID, err)
		}
		if len(tokens) == 0 {
			result.SkippedCount++
			fmt.Printf("APR registry skip provider=%s driver=%s reason=no_active_tokens\n", strings.TrimSpace(providerView.ID), driverKind)
			continue
		}

		providerInstance, err := buildProvider(ctx, providerLifecycle, loader, providerView, modelsByProvider[strings.TrimSpace(providerView.ID)], tokens, clients, transporter)
		if err != nil {
			cleanupFunctions(cleanups)
			return LoadResult{}, nil, nil, fmt.Errorf("build provider %s: %w", providerView.ID, err)
		}
		if providerInstance.provider == nil {
			result.SkippedCount++
			fmt.Printf("APR registry skip provider=%s driver=%s reason=nil_provider\n", strings.TrimSpace(providerView.ID), driverKind)
			continue
		}
		fmt.Printf("APR registry load provider=%s driver=%s route_models=%d tokens=%d\n", strings.TrimSpace(providerView.ID), driverKind, len(modelsByProvider[strings.TrimSpace(providerView.ID)]), len(tokens))
		if providerInstance.cleanup != nil {
			cleanups = append(cleanups, providerInstance.cleanup)
		}
		if providerInstance.codex != nil && providerInstance.runtimeKey != "" {
			codexProviders[providerInstance.runtimeKey] = providerInstance.codex
		}
		loadedProviders = append(loadedProviders, providerInstance)
		providerMap[providerView.ID] = providerView
		result.ProviderCount++
		result.TokenCount += len(tokens)
	}

	dispatcher.Reset()
	for _, provider := range loadedProviders {
		dispatcher.AddProvider(provider.provider)
	}
	dispatcher.SetRoutes(buildRoutes(providers, routeViews, groupViews, providerMap, &result))
	dispatcher.Sync()
	return result, cleanups, codexProviders, nil
}

func buildProvider(ctx context.Context, providerLifecycle context.Context, loader OrganizationProviderLoader, providerView organization.OrganizationProviderView, routeModels []string, tokens []organization.OrganizationProviderTokenSecret, clients ProviderHTTPClients, transporter types.HTTPProxy) (builtProvider, error) {
	switch strings.TrimSpace(providerView.DriverKind) {
	case "copilot":
		return buildCopilotProvider(providerLifecycle, providerView, routeModels, tokens, clients, transporter)
	case "codex":
		return buildCodexProvider(ctx, providerLifecycle, loader, providerView, routeModels, tokens, clients, transporter)
	default:
		return buildForwardAPRProvider(providerView, routeModels, tokens, clients.Upstream)
	}
}

func buildCopilotProvider(providerLifecycle context.Context, providerView organization.OrganizationProviderView, routeModels []string, tokens []organization.OrganizationProviderTokenSecret, clients ProviderHTTPClients, transporter types.HTTPProxy) (builtProvider, error) {
	githubTokens := make([]string, 0, len(tokens))
	for _, token := range tokens {
		if trimmed := strings.TrimSpace(token.Token); trimmed != "" {
			githubTokens = append(githubTokens, trimmed)
		}
	}
	if len(githubTokens) == 0 {
		return builtProvider{}, fmt.Errorf("no copilot tokens configured")
	}

	provider, err := copilot.Setup(&copilot.CopilotConfig{
		Tokens: githubTokens,
	}, transporter, copilot.WithHTTPClient(clients.Upstream), copilot.WithSetupHTTPClient(clients.Setup), copilot.WithLifecycleContext(providerLifecycle))
	if err != nil {
		return builtProvider{}, err
	}
	chatProvider, ok := any(provider).(types.OpenAICompatible)
	if !ok {
		_ = provider.Close()
		return builtProvider{}, fmt.Errorf("copilot provider does not implement OpenAI-compatible chat")
	}
	embeddingProvider, ok := any(provider).(types.EmbeddingCompatible)
	if !ok {
		_ = provider.Close()
		return builtProvider{}, fmt.Errorf("copilot provider does not implement embeddings")
	}
	base := namedAPRProviderBase{
		name:   strings.TrimSpace(providerView.ID),
		models: append([]string(nil), routeModels...),
		base:   provider,
	}
	return builtProvider{
		provider: namedOpenAIEmbeddingProvider{
			namedOpenAIProvider: namedOpenAIProvider{
				namedAPRProviderBase: base,
				chat:                 chatProvider,
			},
			embedding: embeddingProvider,
		},
		cleanup: func() {
			_ = provider.Close()
		},
	}, nil
}

func buildForwardAPRProvider(providerView organization.OrganizationProviderView, routeModels []string, tokens []organization.OrganizationProviderTokenSecret, httpClient *http.Client) (builtProvider, error) {
	tokenStates := toTokenStates(tokens)
	if len(tokenStates) == 0 {
		return builtProvider{}, fmt.Errorf("no provider tokens configured")
	}
	modelCache := NewModelCache(NewStaticModelProvider(routeModels))
	if err := modelCache.Initialize(); err != nil {
		return builtProvider{}, fmt.Errorf("initialize model cache: %w", err)
	}
	openAIBaseURL := firstNonEmpty(
		providerView.Endpoints.ResponsesBaseURL,
		providerView.Endpoints.MessagesBaseURL,
		providerView.Endpoints.EmbeddingsBaseURL,
		providerView.BaseURL,
	)
	provider := NewForwardProvider(ForwardProviderConfig{
		Name: strings.TrimSpace(providerView.ID),
		BaseURL: firstNonEmpty(
			providerView.BaseURL,
			providerView.Endpoints.MessagesBaseURL,
			providerView.Endpoints.ResponsesBaseURL,
			providerView.Endpoints.EmbeddingsBaseURL,
		),
		WireAPIs:   providerWireAPIs(providerView.Capabilities),
		TokenPool:  NewTokenPool(tokenStates, &RoundRobinSelector{}),
		ModelCache: modelCache,
		HTTPClient: httpClient,
		ChatEndpoint: optionalWireEndpoint(
			providerView.Capabilities.OpenAICompatible,
			openAIBaseURL,
			"/chat/completions",
		),
		AnthropicEndpoint:  optionalWireEndpoint(providerView.Capabilities.AnthropicCompatible, firstNonEmpty(providerView.Endpoints.MessagesBaseURL, providerView.BaseURL), "/v1/messages"),
		ResponsesEndpoint:  optionalWireEndpoint(providerView.Capabilities.ResponsesCompatible, firstNonEmpty(providerView.Endpoints.ResponsesBaseURL, providerView.BaseURL), "/responses"),
		EmbeddingsEndpoint: optionalWireEndpoint(providerView.Capabilities.EmbeddingCompatible, firstNonEmpty(providerView.Endpoints.EmbeddingsBaseURL, providerView.BaseURL), "/embeddings"),
	})
	return builtProvider{provider: adaptProvider(provider)}, nil
}

func buildCodexProvider(ctx context.Context, providerLifecycle context.Context, loader OrganizationProviderLoader, providerView organization.OrganizationProviderView, routeModels []string, tokens []organization.OrganizationProviderTokenSecret, clients ProviderHTTPClients, transporter types.HTTPProxy) (builtProvider, error) {
	primaryToken, authTokens, fallbackTokens, ok := splitCodexTokens(tokens)
	if !ok {
		return builtProvider{}, fmt.Errorf("no usable codex credentials configured")
	}

	cfg := buildCodexConfig(providerView, primaryToken)
	cfg.AuthFiles = make([]codex.CodexAuthConfig, 0, len(authTokens))
	for _, token := range authTokens {
		cfg.AuthFiles = append(cfg.AuthFiles, buildCodexAuthConfig(token))
	}
	cfg.Fallbacks = make([]codex.CodexConfig, 0, len(fallbackTokens))
	for _, token := range fallbackTokens {
		cfg.Fallbacks = append(cfg.Fallbacks, buildCodexConfig(providerView, token))
	}

	provider, err := codex.Setup(&cfg, transporter, ctx, codex.WithHTTPClient(clients.Upstream), codex.WithTokenHTTPClient(clients.Setup), codex.WithLifecycleContext(providerLifecycle), codex.WithConfigPersistence(func(updated *codex.CodexConfig) error {
		return persistCodexCredentialState(loader, providerView, primaryToken, authTokens, fallbackTokens, updated)
	}))
	if err != nil {
		return builtProvider{}, err
	}
	chatProvider, ok := any(provider).(types.OpenAICompatible)
	if !ok {
		_ = provider.Close()
		return builtProvider{}, fmt.Errorf("codex provider does not implement OpenAI-compatible chat")
	}
	responsesProvider, ok := any(provider).(types.ResponsesCompatible)
	if !ok {
		_ = provider.Close()
		return builtProvider{}, fmt.Errorf("codex provider does not implement responses")
	}
	base := namedAPRProviderBase{
		name:   strings.TrimSpace(providerView.ID),
		models: append([]string(nil), routeModels...),
		base:   provider,
	}
	return builtProvider{
		provider: namedOpenAIResponsesProvider{
			namedOpenAIProvider: namedOpenAIProvider{
				namedAPRProviderBase: base,
				chat:                 chatProvider,
			},
			responses: responsesProvider,
		},
		codex:      provider,
		runtimeKey: codexProviderRuntimeKey(providerView.OrganizationID, providerView.ID),
		cleanup: func() {
			_ = provider.Close()
		},
	}, nil
}

func splitCodexTokens(tokens []organization.OrganizationProviderTokenSecret) (organization.OrganizationProviderTokenSecret, []organization.OrganizationProviderTokenSecret, []organization.OrganizationProviderTokenSecret, bool) {
	var primary organization.OrganizationProviderTokenSecret
	primarySet := false
	explicitAuth := make([]organization.OrganizationProviderTokenSecret, 0)
	deferred := make([]organization.OrganizationProviderTokenSecret, 0)
	fallbacks := make([]organization.OrganizationProviderTokenSecret, 0)

	for _, token := range tokens {
		if !codexTokenHasUsableSecret(token) {
			continue
		}
		switch strings.ToLower(strings.TrimSpace(token.Config.Role)) {
		case "fallback":
			fallbacks = append(fallbacks, token)
		case "primary":
			if !primarySet {
				primary = token
				primarySet = true
				continue
			}
			explicitAuth = append(explicitAuth, token)
		case "auth":
			explicitAuth = append(explicitAuth, token)
		default:
			deferred = append(deferred, token)
		}
	}

	if !primarySet {
		switch {
		case len(deferred) > 0:
			primary = deferred[0]
			deferred = deferred[1:]
			primarySet = true
		case len(explicitAuth) > 0:
			primary = explicitAuth[0]
			explicitAuth = explicitAuth[1:]
			primarySet = true
		}
	}

	if !primarySet {
		return organization.OrganizationProviderTokenSecret{}, nil, nil, false
	}
	return primary, append(explicitAuth, deferred...), fallbacks, true
}

func buildCodexConfig(providerView organization.OrganizationProviderView, token organization.OrganizationProviderTokenSecret) codex.CodexConfig {
	material := codexRuntimeMaterial(token)
	cfg := codex.CodexConfig{
		ID:                  token.Config.BackendID,
		Name:                firstNonEmpty(token.Config.BackendName, token.Label),
		BaseURL:             firstNonEmpty(token.Config.BaseURL, providerView.BaseURL),
		Active:              token.Config.Active,
		Weight:              token.Config.Weight,
		WeightDiffThreshold: token.Config.WeightDiffThreshold,
		AdaptiveWeights:     token.Config.AdaptiveWeights,
		AllowedAPIKeys:      append([]string(nil), token.Config.AllowedAPIKeys...),
		Mode:                token.Config.Mode,
		RequiresOAuthBeta:   token.Config.RequiresOAuthBeta,
		CustomPrompt:        token.Config.CustomPrompt,
		CustomPromptModels:  append([]string(nil), token.Config.CustomPromptModels...),
		AuthToken:           material.AuthToken,
		AuthJSONContent:     material.AuthJSONContent,
		APIKey:              material.APIKey,
	}
	return cfg
}

func buildCodexAuthConfig(token organization.OrganizationProviderTokenSecret) codex.CodexAuthConfig {
	material := codexRuntimeMaterial(token)
	return codex.CodexAuthConfig{
		ID:                      token.Config.BackendID,
		Name:                    firstNonEmpty(token.Config.BackendName, token.Label),
		Active:                  token.Config.Active,
		Weight:                  token.Config.Weight,
		AllowedAPIKeys:          append([]string(nil), token.Config.AllowedAPIKeys...),
		AuthToken:               material.AuthToken,
		AuthJSONContent:         material.AuthJSONContent,
		AuthJSONPath:            "",
		APIKey:                  material.APIKey,
		Mode:                    firstNonEmpty(token.Config.Mode, inferCodexMode(material)),
		RequiresOAuthBeta:       token.Config.RequiresOAuthBeta,
		RequiresOAuthBetaLegacy: false,
	}
}

func codexRuntimeMaterial(token organization.OrganizationProviderTokenSecret) organization.OrganizationProviderTokenMaterial {
	material := token.Secret
	if strings.TrimSpace(material.Token) == "" && strings.TrimSpace(token.Token) != "" {
		material.Token = token.Token
	}
	switch inferCodexMode(material) {
	case "chatgpt":
		if strings.TrimSpace(material.AuthJSONContent) == "" && strings.TrimSpace(material.AuthToken) == "" && strings.TrimSpace(material.Token) != "" {
			material.AuthToken = material.Token
		}
	case "api_key":
		if strings.TrimSpace(material.APIKey) == "" && strings.TrimSpace(material.Token) != "" {
			material.APIKey = material.Token
		}
	}
	return material
}

func inferCodexMode(material organization.OrganizationProviderTokenMaterial) string {
	switch {
	case strings.TrimSpace(material.AuthJSONContent) != "" || strings.TrimSpace(material.AuthToken) != "":
		return "chatgpt"
	case strings.TrimSpace(material.APIKey) != "" || strings.TrimSpace(material.Token) != "":
		return "api_key"
	default:
		return ""
	}
}

func codexTokenHasUsableSecret(token organization.OrganizationProviderTokenSecret) bool {
	material := codexRuntimeMaterial(token)
	return strings.TrimSpace(material.APIKey) != "" ||
		strings.TrimSpace(material.AuthToken) != "" ||
		strings.TrimSpace(material.AuthJSONContent) != "" ||
		strings.TrimSpace(material.Token) != ""
}

func persistCodexCredentialState(loader OrganizationProviderLoader, providerView organization.OrganizationProviderView, primary organization.OrganizationProviderTokenSecret, authTokens []organization.OrganizationProviderTokenSecret, fallbackTokens []organization.OrganizationProviderTokenSecret, updated *codex.CodexConfig) error {
	if loader == nil || updated == nil {
		return nil
	}
	ctx := context.Background()
	if err := loader.UpdateOrganizationProviderTokenSecret(ctx, providerView.OrganizationID, providerView.ID, primary.ID, organization.UpdateOrganizationProviderTokenSecretParams{
		Secret: organizationProviderTokenMaterialFromCodexConfig(*updated),
	}); err != nil {
		return err
	}
	for i, token := range authTokens {
		if i >= len(updated.AuthFiles) {
			break
		}
		if err := loader.UpdateOrganizationProviderTokenSecret(ctx, providerView.OrganizationID, providerView.ID, token.ID, organization.UpdateOrganizationProviderTokenSecretParams{
			Secret: organizationProviderTokenMaterialFromCodexAuthConfig(updated.AuthFiles[i]),
		}); err != nil {
			return err
		}
	}
	for i, token := range fallbackTokens {
		if i >= len(updated.Fallbacks) {
			break
		}
		if err := loader.UpdateOrganizationProviderTokenSecret(ctx, providerView.OrganizationID, providerView.ID, token.ID, organization.UpdateOrganizationProviderTokenSecretParams{
			Secret: organizationProviderTokenMaterialFromCodexConfig(updated.Fallbacks[i]),
		}); err != nil {
			return err
		}
	}
	return nil
}

func organizationProviderTokenMaterialFromCodexConfig(cfg codex.CodexConfig) organization.OrganizationProviderTokenMaterial {
	secret := organization.OrganizationProviderTokenMaterial{
		APIKey:          strings.TrimSpace(cfg.APIKey),
		AuthToken:       strings.TrimSpace(cfg.AuthToken),
		AuthJSONContent: strings.TrimSpace(cfg.AuthJSONContent),
	}
	if secret.APIKey != "" {
		secret.Token = secret.APIKey
	}
	return secret
}

func organizationProviderTokenMaterialFromCodexAuthConfig(cfg codex.CodexAuthConfig) organization.OrganizationProviderTokenMaterial {
	secret := organization.OrganizationProviderTokenMaterial{
		APIKey:          strings.TrimSpace(cfg.APIKey),
		AuthToken:       strings.TrimSpace(cfg.AuthToken),
		AuthJSONContent: strings.TrimSpace(cfg.AuthJSONContent),
	}
	if secret.APIKey != "" {
		secret.Token = secret.APIKey
	}
	return secret
}

func cleanupFunctions(cleanups []func()) {
	for i := len(cleanups) - 1; i >= 0; i-- {
		if cleanups[i] != nil {
			cleanups[i]()
		}
	}
}

func providerCanUseImplicitBaseURL(driverKind string) bool {
	switch strings.TrimSpace(driverKind) {
	case "copilot", "codex":
		return true
	default:
		return false
	}
}

func providerModelsFromRouteGroups(groupViews []organization.OrganizationProviderRouteGroupView) map[string][]string {
	modelsByProvider := make(map[string][]string)
	seenByProvider := make(map[string]map[string]struct{})
	for _, groupView := range groupViews {
		for _, providerRef := range groupView.ProviderRefs {
			providerID := strings.TrimSpace(providerRef)
			if providerID == "" {
				continue
			}
			seen := seenByProvider[providerID]
			if seen == nil {
				seen = make(map[string]struct{})
				seenByProvider[providerID] = seen
			}
			for _, model := range groupView.Models {
				modelID := strings.TrimSpace(model)
				if modelID == "" {
					continue
				}
				if _, ok := seen[modelID]; ok {
					continue
				}
				seen[modelID] = struct{}{}
				modelsByProvider[providerID] = append(modelsByProvider[providerID], modelID)
			}
		}
	}
	return modelsByProvider
}

func buildRoutes(providers []organization.OrganizationProviderView, routeViews []organization.OrganizationProviderRouteView, groupViews []organization.OrganizationProviderRouteGroupView, providerMap map[string]organization.OrganizationProviderView, result *LoadResult) []runtime.ModelRoute {
	groupsByRouteID := make(map[string][]organization.OrganizationProviderRouteGroupView)
	for _, groupView := range groupViews {
		groupsByRouteID[groupView.RouteID] = append(groupsByRouteID[groupView.RouteID], groupView)
	}
	routes := make([]runtime.ModelRoute, 0, len(routeViews))
	for _, routeView := range routeViews {
		groups := make([]runtime.RouteGroup, 0)
		for _, groupView := range groupsByRouteID[routeView.ID] {
			providerRefs := make([]string, 0, len(groupView.ProviderRefs))
			for _, providerRef := range groupView.ProviderRefs {
				if _, ok := providerMap[providerRef]; ok {
					providerRefs = append(providerRefs, providerRef)
				}
			}
			if len(providerRefs) == 0 {
				continue
			}
			groups = append(groups, runtime.RouteGroup{
				ID:        groupView.ID,
				Models:    append([]string(nil), groupView.Models...),
				Providers: providerRefs,
			})
		}
		if len(groups) == 0 {
			continue
		}
		routes = append(routes, runtime.ModelRoute{ID: routeView.ID, Vendor: strings.TrimSpace(routeView.Vendor), Groups: groups})
		result.RouteCount++
	}
	if len(routes) > 0 {
		return routes
	}
	for _, providerView := range providers {
		vendor := strings.TrimSpace(providerView.ProviderType)
		if vendor == "" || len(providerView.Models) == 0 {
			continue
		}
		routes = append(routes, runtime.ModelRoute{
			ID:     providerView.ID,
			Vendor: vendor,
			Groups: []runtime.RouteGroup{{ID: providerView.ID, Models: providerModelIDs(providerView.Models), Providers: []string{providerView.ID}}},
		})
		result.RouteCount++
	}
	return routes
}

func toTokenStates(tokens []organization.OrganizationProviderTokenSecret) []TokenState {
	result := make([]TokenState, 0, len(tokens))
	for _, token := range tokens {
		trimmed := strings.TrimSpace(token.Token)
		if trimmed == "" {
			continue
		}
		result = append(result, TokenState{Label: token.Label, Token: trimmed, Enabled: true})
	}
	return result
}

func providerModelIDs(models []organization.OrganizationProviderModel) []string {
	result := make([]string, 0, len(models))
	for _, model := range models {
		if id := strings.TrimSpace(model.ID); id != "" {
			result = append(result, id)
		}
	}
	return result
}

func providerWireAPIs(capabilities organization.OrganizationProviderCapabilities) []string {
	wireAPIs := make([]string, 0, 4)
	if capabilities.OpenAICompatible {
		wireAPIs = append(wireAPIs, WireAIOpenAI)
	}
	if capabilities.ResponsesCompatible {
		wireAPIs = append(wireAPIs, WireAPIResponses)
	}
	if capabilities.AnthropicCompatible {
		wireAPIs = append(wireAPIs, WireAPIAnthropic)
	}
	if len(wireAPIs) == 0 {
		return []string{WireAIOpenAI}
	}
	return wireAPIs
}

func optionalWireEndpoint(enabled bool, baseURL string, path string) *WireEndpoint {
	if !enabled {
		return nil
	}
	trimmed := strings.TrimSpace(baseURL)
	if trimmed == "" {
		return nil
	}
	return &WireEndpoint{BaseURL: trimmed, Path: path}
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
