package usecase

import (
	"context"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	settingdomain "haixun-backend/internal/model/setting/domain/usecase"
	domusecase "haixun-backend/internal/model/threads_account/domain/usecase"
)

const keyAiCredentials = "ai.credentials"

var knownAiProviders = []string{
	"opencode-go",
	"xai",
	"openai",
	"anthropic",
	"google",
}

func (u *threadsAccountUseCase) GetAiSettings(ctx context.Context, tenantID, ownerUID, accountID string) (*domusecase.AiSettings, error) {
	account, err := u.assertOwned(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	stored, err := u.loadAiCredentials(ctx, ownerUID, account.ID)
	if err != nil {
		return nil, err
	}
	return toPublicAiSettings(account.ID, stored), nil
}

func (u *threadsAccountUseCase) ResolveMemberAiCredential(
	ctx context.Context,
	tenantID, ownerUID string,
) (*domusecase.WorkerAiCredential, error) {
	if err := requireActor(tenantID, ownerUID); err != nil {
		return nil, err
	}
	stored, err := u.loadAiCredentials(ctx, ownerUID, "")
	if err != nil {
		return nil, err
	}
	provider := strings.TrimSpace(stored.Provider)
	model := strings.TrimSpace(stored.Model)
	apiKey := strings.TrimSpace(stored.ApiKeys[provider])
	if provider == "" || apiKey == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("請先在設定頁設定 AI API key")
	}
	return &domusecase.WorkerAiCredential{
		Provider: provider,
		Model:    model,
		APIKey:   apiKey,
	}, nil
}

func (u *threadsAccountUseCase) ResolveWorkerAiCredential(
	ctx context.Context,
	tenantID, ownerUID, accountID string,
) (*domusecase.WorkerAiCredential, error) {
	account, err := u.assertOwned(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	stored, err := u.loadAiCredentials(ctx, ownerUID, account.ID)
	if err != nil {
		return nil, err
	}
	provider := strings.TrimSpace(stored.ResearchProvider)
	model := strings.TrimSpace(stored.ResearchModel)
	if provider == "" {
		provider = strings.TrimSpace(stored.Provider)
	}
	if model == "" {
		model = strings.TrimSpace(stored.Model)
	}
	apiKey := strings.TrimSpace(stored.ApiKeys[provider])
	if apiKey == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("AI API key is not configured for provider " + provider)
	}
	return &domusecase.WorkerAiCredential{
		Provider: provider,
		Model:    model,
		APIKey:   apiKey,
	}, nil
}

func (u *threadsAccountUseCase) UpdateAiSettings(
	ctx context.Context,
	tenantID, ownerUID, accountID string,
	patch domusecase.AiSettingsPatch,
) (*domusecase.AiSettings, error) {
	account, err := u.assertOwned(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	current, err := u.loadAiCredentials(ctx, ownerUID, account.ID)
	if err != nil {
		return nil, err
	}
	next := applyAiSettingsPatch(current, patch)
	if err := u.saveAiCredentials(ctx, ownerUID, next); err != nil {
		return nil, err
	}
	return toPublicAiSettings(account.ID, next), nil
}

type aiCredentials struct {
	Provider         string
	Model            string
	ResearchProvider string
	ResearchModel    string
	ApiKeys          map[string]string
}

func defaultAiCredentials() aiCredentials {
	return aiCredentials{
		Provider:         "opencode-go",
		Model:            "deepseek-v4-pro",
		ResearchProvider: "opencode-go",
		ResearchModel:    "deepseek-v4-flash",
		ApiKeys:          map[string]string{},
	}
}

func (u *threadsAccountUseCase) loadAiCredentials(ctx context.Context, ownerUID, accountID string) (aiCredentials, error) {
	defaults := defaultAiCredentials()
	setting, err := u.settings.Get(ctx, settingScopeUser, ownerUID, keyAiCredentials)
	if err == nil {
		return mergeAiCredentials(defaults, setting.Value), nil
	}
	if !isSettingNotFound(err) {
		return defaults, err
	}
	// Legacy: per-account storage migrates to user scope on first read.
	legacy, err := u.settings.Get(ctx, settingScopeAccount, accountID, keyAiCredentials)
	if err != nil {
		if isSettingNotFound(err) {
			return defaults, nil
		}
		return defaults, err
	}
	creds := mergeAiCredentials(defaults, legacy.Value)
	if saveErr := u.saveAiCredentials(ctx, ownerUID, creds); saveErr != nil {
		return creds, saveErr
	}
	return creds, nil
}

func (u *threadsAccountUseCase) saveAiCredentials(ctx context.Context, ownerUID string, creds aiCredentials) error {
	_, err := u.settings.Upsert(ctx, settingdomain.UpsertRequest{
		Scope:   settingScopeUser,
		ScopeID: ownerUID,
		Key:     keyAiCredentials,
		Value:   aiCredentialsToMap(creds),
	})
	return err
}

func mergeAiCredentials(defaults aiCredentials, value map[string]interface{}) aiCredentials {
	if value == nil {
		return defaults
	}
	if v, ok := value["provider"].(string); ok && strings.TrimSpace(v) != "" {
		defaults.Provider = v
	}
	if v, ok := value["model"].(string); ok && strings.TrimSpace(v) != "" {
		defaults.Model = v
	}
	if v, ok := value["research_provider"].(string); ok && strings.TrimSpace(v) != "" {
		defaults.ResearchProvider = v
	}
	if v, ok := value["research_model"].(string); ok && strings.TrimSpace(v) != "" {
		defaults.ResearchModel = v
	}
	if raw, ok := value["api_keys"].(map[string]interface{}); ok {
		keys := map[string]string{}
		for provider, item := range raw {
			if s, ok := item.(string); ok && strings.TrimSpace(s) != "" {
				keys[provider] = strings.TrimSpace(s)
			}
		}
		defaults.ApiKeys = keys
	}
	return defaults
}

func applyAiSettingsPatch(current aiCredentials, patch domusecase.AiSettingsPatch) aiCredentials {
	if patch.Provider != nil && strings.TrimSpace(*patch.Provider) != "" {
		current.Provider = strings.TrimSpace(*patch.Provider)
	}
	if patch.Model != nil && strings.TrimSpace(*patch.Model) != "" {
		current.Model = strings.TrimSpace(*patch.Model)
	}
	if patch.ResearchProvider != nil && strings.TrimSpace(*patch.ResearchProvider) != "" {
		current.ResearchProvider = strings.TrimSpace(*patch.ResearchProvider)
	}
	if patch.ResearchModel != nil && strings.TrimSpace(*patch.ResearchModel) != "" {
		current.ResearchModel = strings.TrimSpace(*patch.ResearchModel)
	}
	if len(patch.ApiKeys) > 0 {
		if current.ApiKeys == nil {
			current.ApiKeys = map[string]string{}
		}
		for provider, value := range patch.ApiKeys {
			trimmed := strings.TrimSpace(value)
			if trimmed == "" || isMaskedAPIKey(trimmed) {
				continue
			}
			current.ApiKeys[provider] = trimmed
		}
	}
	return current
}

func aiCredentialsToMap(creds aiCredentials) map[string]interface{} {
	keys := map[string]interface{}{}
	for provider, value := range creds.ApiKeys {
		keys[provider] = value
	}
	return map[string]interface{}{
		"provider":          creds.Provider,
		"model":             creds.Model,
		"research_provider": creds.ResearchProvider,
		"research_model":    creds.ResearchModel,
		"api_keys":          keys,
	}
}

func toPublicAiSettings(accountID string, creds aiCredentials) *domusecase.AiSettings {
	masked := map[string]string{}
	configured := map[string]bool{}
	for _, provider := range knownAiProviders {
		raw := strings.TrimSpace(creds.ApiKeys[provider])
		configured[provider] = raw != ""
		if maskedValue := maskAPIKey(raw); maskedValue != "" {
			masked[provider] = maskedValue
		}
	}
	return &domusecase.AiSettings{
		AccountID:         accountID,
		Provider:          creds.Provider,
		Model:             creds.Model,
		ResearchProvider:  creds.ResearchProvider,
		ResearchModel:     creds.ResearchModel,
		ApiKeys:           masked,
		ApiKeysConfigured: configured,
	}
}

func maskAPIKey(key string) string {
	trimmed := strings.TrimSpace(key)
	if trimmed == "" {
		return ""
	}
	if len(trimmed) <= 4 {
		return "••••"
	}
	return "••••" + trimmed[len(trimmed)-4:]
}

func isMaskedAPIKey(value string) bool {
	return strings.HasPrefix(value, "••••")
}
