package organization

import (
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

func scanOrganizationProviderView(scanner interface {
	Scan(...any) error
}) (OrganizationProviderView, error) {
	var provider OrganizationProviderView
	var endpointsRaw []byte
	var capabilitiesRaw []byte
	var modelsRaw []byte
	if err := scanner.Scan(
		&provider.ID,
		&provider.OrganizationID,
		&provider.Name,
		&provider.Description,
		&provider.DriverKind,
		&provider.BaseURL,
		&endpointsRaw,
		&capabilitiesRaw,
		&modelsRaw,
		&provider.Status,
		&provider.CreatedAt,
		&provider.UpdatedAt,
	); err != nil {
		return OrganizationProviderView{}, err
	}
	endpoints, err := decodeProviderEndpoints(endpointsRaw)
	if err != nil {
		return OrganizationProviderView{}, err
	}
	capabilities, err := decodeProviderCapabilities(capabilitiesRaw)
	if err != nil {
		return OrganizationProviderView{}, err
	}
	models, err := decodeOrganizationProviderModels(modelsRaw)
	if err != nil {
		return OrganizationProviderView{}, err
	}
	provider.Endpoints = endpoints
	provider.Capabilities = capabilities
	provider.Models = models
	provider.ProviderType = inferOrganizationProviderRouteVendor(capabilities)
	return provider, nil
}

func scanOrganizationWorkspaceProviderGrantView(scanner interface {
	Scan(...any) error
}) (OrganizationWorkspaceProviderGrantView, error) {
	var grant OrganizationWorkspaceProviderGrantView
	if err := scanner.Scan(
		&grant.WorkspaceID,
		&grant.ProviderID,
		&grant.ProviderName,
		&grant.DriverKind,
		&grant.BaseURL,
		&grant.Status,
		&grant.GrantedAt,
		&grant.LastUpdatedAt,
	); err != nil {
		return OrganizationWorkspaceProviderGrantView{}, err
	}
	grant.ProviderType = grant.DriverKind
	return grant, nil
}

func scanOrganizationWorkspaceRouteGrantView(scanner interface {
	Scan(...any) error
}) (OrganizationWorkspaceRouteGrantView, error) {
	var grant OrganizationWorkspaceRouteGrantView
	var modelsRaw []byte
	if err := scanner.Scan(
		&grant.WorkspaceID,
		&grant.RouteID,
		&grant.Vendor,
		&grant.DisplayName,
		&modelsRaw,
		&grant.Status,
		&grant.GrantedAt,
		&grant.LastUpdatedAt,
	); err != nil {
		return OrganizationWorkspaceRouteGrantView{}, err
	}
	models, err := decodeOrganizationProviderModels(modelsRaw)
	if err != nil {
		return OrganizationWorkspaceRouteGrantView{}, err
	}
	grant.Models = models
	return grant, nil
}

func normalizeProviderEndpoints(endpoints OrganizationProviderEndpoints) OrganizationProviderEndpoints {
	return OrganizationProviderEndpoints{
		MessagesBaseURL:   strings.TrimSpace(endpoints.MessagesBaseURL),
		ResponsesBaseURL:  strings.TrimSpace(endpoints.ResponsesBaseURL),
		EmbeddingsBaseURL: strings.TrimSpace(endpoints.EmbeddingsBaseURL),
	}
}

func encodeProviderEndpoints(endpoints OrganizationProviderEndpoints) (string, error) {
	normalized := normalizeProviderEndpoints(endpoints)
	if normalized == (OrganizationProviderEndpoints{}) {
		return "", nil
	}
	payload := model.ProviderEndpoints{
		MessagesBaseURL:   normalized.MessagesBaseURL,
		ResponsesBaseURL:  normalized.ResponsesBaseURL,
		EmbeddingsBaseURL: normalized.EmbeddingsBaseURL,
	}
	return model.MarshalJSONString("provider endpoints", payload)
}

func decodeProviderEndpoints(data []byte) (OrganizationProviderEndpoints, error) {
	var payload model.ProviderEndpoints
	if err := model.UnmarshalJSONObject("provider endpoints", data, &payload); err != nil {
		return OrganizationProviderEndpoints{}, err
	}
	endpoints := OrganizationProviderEndpoints{
		MessagesBaseURL:   payload.MessagesBaseURL,
		ResponsesBaseURL:  payload.ResponsesBaseURL,
		EmbeddingsBaseURL: payload.EmbeddingsBaseURL,
	}
	return normalizeProviderEndpoints(endpoints), nil
}

func normalizeProviderCapabilities(capabilities OrganizationProviderCapabilities) OrganizationProviderCapabilities {
	return OrganizationProviderCapabilities{
		OpenAICompatible:    capabilities.OpenAICompatible,
		AnthropicCompatible: capabilities.AnthropicCompatible,
		ResponsesCompatible: capabilities.ResponsesCompatible,
		EmbeddingCompatible: capabilities.EmbeddingCompatible,
	}
}

func hasProviderCapabilities(capabilities OrganizationProviderCapabilities) bool {
	return capabilities.OpenAICompatible || capabilities.AnthropicCompatible || capabilities.ResponsesCompatible || capabilities.EmbeddingCompatible
}

func inferProviderCapabilities(driverKind string, baseURL string, endpoints OrganizationProviderEndpoints, models []OrganizationProviderModel, requested OrganizationProviderCapabilities) OrganizationProviderCapabilities {
	capabilities := normalizeProviderCapabilities(requested)
	if hasProviderCapabilities(capabilities) {
		return capabilities
	}

	switch strings.TrimSpace(driverKind) {
	case "codex":
		return OrganizationProviderCapabilities{ResponsesCompatible: true}
	case "claude":
		return OrganizationProviderCapabilities{AnthropicCompatible: true}
	case "opencode", "builtin":
		return OrganizationProviderCapabilities{AnthropicCompatible: true}
	}

	endpoints = normalizeProviderEndpoints(endpoints)
	if strings.TrimSpace(endpoints.ResponsesBaseURL) != "" {
		capabilities.ResponsesCompatible = true
	}
	if strings.TrimSpace(endpoints.MessagesBaseURL) != "" {
		capabilities.AnthropicCompatible = true
	}
	if strings.TrimSpace(endpoints.EmbeddingsBaseURL) != "" {
		capabilities.EmbeddingCompatible = true
	}
	if hasProviderCapabilities(capabilities) {
		return capabilities
	}

	if providerLooksAnthropic(baseURL, models) {
		return OrganizationProviderCapabilities{AnthropicCompatible: true}
	}

	if providerLooksEmbedding(models) {
		return OrganizationProviderCapabilities{OpenAICompatible: true, EmbeddingCompatible: true}
	}

	return OrganizationProviderCapabilities{OpenAICompatible: true}
}

func providerLooksAnthropic(baseURL string, models []OrganizationProviderModel) bool {
	if providerTextContains(baseURL, "anthropic", "claude") {
		return true
	}
	for _, model := range models {
		if providerTextContains(model.ID, "anthropic", "claude") || providerTextContains(model.Name, "anthropic", "claude") {
			return true
		}
	}
	return false
}

func providerLooksEmbedding(models []OrganizationProviderModel) bool {
	for _, model := range models {
		if providerTextContains(model.ID, "embedding", "embeddings", "embed") || providerTextContains(model.Name, "embedding", "embeddings", "embed") {
			return true
		}
	}
	return false
}

func providerTextContains(value string, needles ...string) bool {
	normalized := strings.ToLower(strings.TrimSpace(value))
	for _, needle := range needles {
		if strings.Contains(normalized, needle) {
			return true
		}
	}
	return false
}

func encodeProviderCapabilities(capabilities OrganizationProviderCapabilities) (string, error) {
	normalized := normalizeProviderCapabilities(capabilities)
	payload := model.ProviderCapabilities{
		OpenAICompatible:    normalized.OpenAICompatible,
		AnthropicCompatible: normalized.AnthropicCompatible,
		ResponsesCompatible: normalized.ResponsesCompatible,
		EmbeddingCompatible: normalized.EmbeddingCompatible,
	}
	return model.MarshalJSONString("provider capabilities", payload)
}

func decodeProviderCapabilities(data []byte) (OrganizationProviderCapabilities, error) {
	var payload model.ProviderCapabilities
	if err := model.UnmarshalJSONObject("provider capabilities", data, &payload); err != nil {
		return OrganizationProviderCapabilities{}, err
	}
	capabilities := OrganizationProviderCapabilities{
		OpenAICompatible:    payload.OpenAICompatible,
		AnthropicCompatible: payload.AnthropicCompatible,
		ResponsesCompatible: payload.ResponsesCompatible,
		EmbeddingCompatible: payload.EmbeddingCompatible,
	}
	return normalizeProviderCapabilities(capabilities), nil
}

func inferOrganizationProviderRouteVendor(capabilities OrganizationProviderCapabilities) string {
	switch {
	case capabilities.OpenAICompatible || capabilities.ResponsesCompatible || capabilities.EmbeddingCompatible:
		return "openai"
	case capabilities.AnthropicCompatible:
		return "anthropic"
	default:
		return ""
	}
}

func encodeJSONStringList(items []string) (string, error) {
	normalized := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, item := range items {
		trimmed := strings.TrimSpace(item)
		if trimmed == "" {
			continue
		}
		if _, ok := seen[trimmed]; ok {
			continue
		}
		seen[trimmed] = struct{}{}
		normalized = append(normalized, trimmed)
	}
	return model.MarshalJSONString("string list", normalized)
}

func decodeJSONStringList(raw []byte) ([]string, error) {
	var items []string
	if err := model.UnmarshalJSONObject("string list", raw, &items); err != nil {
		return nil, err
	}
	normalized := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, item := range items {
		trimmedItem := strings.TrimSpace(item)
		if trimmedItem == "" {
			continue
		}
		if _, ok := seen[trimmedItem]; ok {
			continue
		}
		seen[trimmedItem] = struct{}{}
		normalized = append(normalized, trimmedItem)
	}
	return normalized, nil
}

func normalizeOrganizationProviderModels(models []OrganizationProviderModel) []OrganizationProviderModel {
	normalized := make([]OrganizationProviderModel, 0, len(models))
	seen := make(map[string]struct{}, len(models))
	for _, model := range models {
		id := strings.TrimSpace(model.ID)
		if id == "" {
			continue
		}
		name := strings.TrimSpace(model.Name)
		if name == "" {
			name = id
		}
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		normalized = append(normalized, OrganizationProviderModel{
			Name: name,
			ID:   id,
		})
	}
	return normalized
}

func encodeOrganizationProviderModels(models []OrganizationProviderModel) (string, error) {
	normalized := normalizeOrganizationProviderModels(models)
	payloads := make([]model.ProviderModel, 0, len(normalized))
	for _, m := range normalized {
		payloads = append(payloads, model.ProviderModel{
			Name: m.Name,
			ID:   m.ID,
		})
	}
	return model.MarshalJSONString("provider models", payloads)
}

func decodeOrganizationProviderModels(data []byte) ([]OrganizationProviderModel, error) {
	var payloads []model.ProviderModel
	if err := model.UnmarshalJSONObject("provider models", data, &payloads); err != nil {
		return nil, err
	}
	models := make([]OrganizationProviderModel, 0, len(payloads))
	for _, p := range payloads {
		models = append(models, OrganizationProviderModel{Name: p.Name, ID: p.ID})
	}
	return normalizeOrganizationProviderModels(models), nil
}

func applyOrganizationProviderModelNames(routeModels []OrganizationProviderModel, catalogModels []OrganizationProviderModel) []OrganizationProviderModel {
	if len(routeModels) == 0 {
		return []OrganizationProviderModel{}
	}
	nameByID := make(map[string]string, len(catalogModels))
	for _, model := range catalogModels {
		modelID := strings.TrimSpace(model.ID)
		if modelID == "" {
			continue
		}
		modelName := strings.TrimSpace(model.Name)
		if modelName == "" {
			modelName = modelID
		}
		nameByID[modelID] = modelName
	}
	result := make([]OrganizationProviderModel, 0, len(routeModels))
	for _, model := range routeModels {
		modelID := strings.TrimSpace(model.ID)
		if modelID == "" {
			continue
		}
		modelName := strings.TrimSpace(model.Name)
		if catalogName := strings.TrimSpace(nameByID[modelID]); catalogName != "" {
			modelName = catalogName
		}
		if modelName == "" {
			modelName = modelID
		}
		result = append(result, OrganizationProviderModel{Name: modelName, ID: modelID})
	}
	return result
}

func scanOrganizationProviderRouteView(scanner interface {
	Scan(...any) error
}) (OrganizationProviderRouteView, error) {
	var route OrganizationProviderRouteView
	var modelsRaw []byte
	if err := scanner.Scan(
		&route.ID,
		&route.OrganizationID,
		&route.Vendor,
		&route.DisplayName,
		&modelsRaw,
		&route.SourceKind,
		&route.Status,
		&route.CreatedAt,
		&route.UpdatedAt,
	); err != nil {
		return OrganizationProviderRouteView{}, err
	}
	models, err := decodeOrganizationProviderModels(modelsRaw)
	if err != nil {
		return OrganizationProviderRouteView{}, err
	}
	route.Models = models
	return route, nil
}

func scanOrganizationProviderRouteGroupView(scanner interface {
	Scan(...any) error
}) (OrganizationProviderRouteGroupView, error) {
	var view OrganizationProviderRouteGroupView
	var modelsRaw []byte
	var providerRefsRaw []byte
	if err := scanner.Scan(
		&view.ID,
		&view.OrganizationID,
		&view.RouteID,
		&modelsRaw,
		&providerRefsRaw,
		&view.Strategy,
		&view.Priority,
		&view.ManagedBySystem,
		&view.Status,
		&view.CreatedAt,
		&view.UpdatedAt,
	); err != nil {
		return OrganizationProviderRouteGroupView{}, err
	}
	models, err := decodeJSONStringList(modelsRaw)
	if err != nil {
		return OrganizationProviderRouteGroupView{}, err
	}
	providerRefs, err := decodeJSONStringList(providerRefsRaw)
	if err != nil {
		return OrganizationProviderRouteGroupView{}, err
	}
	view.Models = models
	view.ProviderRefs = providerRefs
	return view, nil
}

func resolveOrganizationProviderStatus(requestedStatus string) (string, error) {
	switch strings.TrimSpace(requestedStatus) {
	case organizationProviderStatusActive:
		return organizationProviderStatusActive, nil
	case organizationProviderStatusInactive:
		return organizationProviderStatusInactive, nil
	default:
		return "", badRequest("invalid organization provider status")
	}
}

func resolveOrganizationProviderDriverKind(requested string) string {
	switch strings.TrimSpace(requested) {
	case "", "forward":
		return "forward"
	default:
		return strings.TrimSpace(requested)
	}
}

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