package agent

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type ModelProfile struct {
	ID               string
	ProviderID       string
	UserID           string
	DisplayName      string
	Model            string
	BaseURL          string
	APIKey           string
	WireAPI          string
	APIKeyConfigured bool
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

type CreateModelProfileParams struct {
	DisplayName string
	Models      []string
	Model       string
	BaseURL     string
	APIKey      string
	WireAPI     string
}

type UpdateModelProfileParams struct {
	DisplayName string
	Models      []string
	Model       string
	BaseURL     string
	APIKey      string
	WireAPI     string
}

func (service *Service) ListModelProfilesForUser(ctx context.Context, userID string) ([]ModelProfile, error) {
	rows, err := service.db.Query(ctx, `
SELECT
  ump.id,
  COALESCE(ump.provider_id, '') AS provider_id,
  ump.user_id,
  COALESCE(umpv.display_name, ump.display_name) AS display_name,
  ump.model,
  COALESCE(umpv.llm_base_url, ump.llm_base_url) AS llm_base_url,
  COALESCE(umpv.llm_api_key_ciphertext, ump.llm_api_key_ciphertext) AS llm_api_key_ciphertext,
  COALESCE(umpv.llm_wire_api, ump.llm_wire_api) AS llm_wire_api,
  ump.created_at,
  ump.updated_at
FROM user_model_profiles ump
LEFT JOIN user_model_providers umpv
  ON umpv.user_id = ump.user_id
 AND umpv.id = ump.provider_id
WHERE ump.user_id = $1
ORDER BY COALESCE(umpv.updated_at, ump.updated_at) DESC, ump.model ASC, ump.id DESC
`, strings.TrimSpace(userID))
	if err != nil {
		return nil, fmt.Errorf("list user model profiles: %w", err)
	}
	defer rows.Close()

	profiles := make([]ModelProfile, 0)
	for rows.Next() {
		profile, err := service.scanModelProfile(rows, false)
		if err != nil {
			return nil, err
		}
		profiles = append(profiles, profile)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate user model profiles: %w", err)
	}
	return profiles, nil
}

func (service *Service) CreateModelProfileForUser(ctx context.Context, userID string, params CreateModelProfileParams) ([]ModelProfile, error) {
	displayName := strings.TrimSpace(params.DisplayName)
	if displayName == "" {
		return nil, storeerr.BadRequest("displayName is required")
	}
	models := normalizeModelProfileModels(params.Models)
	if len(models) == 0 {
		models = normalizeModelProfileModels([]string{params.Model})
	}
	if len(models) == 0 {
		return nil, storeerr.BadRequest("model is required")
	}
	providerID, err := identifiers.NewEntityID("umpvd")
	if err != nil {
		return nil, fmt.Errorf("generate model provider id: %w", err)
	}
	userID = strings.TrimSpace(userID)
	apiKeyCiphertext := ""
	if strings.TrimSpace(params.APIKey) != "" {
		apiKeyCiphertext, err = EncryptAgentCredential(service.credentialStorageKey, params.APIKey)
		if err != nil {
			return nil, err
		}
	}
	now := time.Now().UTC()
	tx, err := service.db.BeginTx(ctx, coredb.TxOptions{})
	if err != nil {
		return nil, fmt.Errorf("begin create model provider: %w", err)
	}
	defer tx.Rollback(ctx)

	if _, err := tx.Exec(ctx, `
INSERT INTO user_model_providers (
  id, user_id, display_name, llm_base_url, llm_api_key_ciphertext, llm_wire_api, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $7)
`, providerID, userID, displayName, strings.TrimSpace(params.BaseURL), apiKeyCiphertext, strings.TrimSpace(params.WireAPI), now); err != nil {
		if coredb.IsUniqueViolation(err) {
			return nil, storeerr.Conflict("a model provider with that name already exists")
		}
		return nil, fmt.Errorf("insert user model provider: %w", err)
	}
	profileIDs := make([]string, 0, len(models))
	values := make([]string, 0, len(models))
	args := make([]any, 0, len(models)*6)
	for _, model := range models {
		profileID, err := identifiers.NewEntityID("ump")
		if err != nil {
			return nil, fmt.Errorf("generate model profile id: %w", err)
		}
		base := len(args) + 1
		values = append(values, fmt.Sprintf("($%d, $%d, $%d, $%d, $%d, '', '', '', $%d, $%d)", base, base+1, base+2, base+3, base+4, base+5, base+5))
		args = append(args, profileID, providerID, userID, model, model, now)
		profileIDs = append(profileIDs, profileID)
	}
	if _, err := tx.Exec(ctx, fmt.Sprintf(`
INSERT INTO user_model_profiles (
  id, provider_id, user_id, display_name, model, llm_base_url, llm_api_key_ciphertext, llm_wire_api, created_at, updated_at
) VALUES %s
`, strings.Join(values, ",\n")), args...); err != nil {
		return nil, fmt.Errorf("insert user model profiles: %w", err)
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, fmt.Errorf("commit create model provider: %w", err)
	}
	profiles := make([]ModelProfile, 0, len(profileIDs))
	for _, profileID := range profileIDs {
		profile, err := service.GetModelProfileForUser(ctx, userID, profileID)
		if err != nil {
			return nil, err
		}
		profiles = append(profiles, profile)
	}
	return profiles, nil
}

func (service *Service) GetModelProfileForUser(ctx context.Context, userID string, profileID string) (ModelProfile, error) {
	row := service.db.QueryRow(ctx, `
SELECT
  ump.id,
  COALESCE(ump.provider_id, '') AS provider_id,
  ump.user_id,
  COALESCE(umpv.display_name, ump.display_name) AS display_name,
  ump.model,
  COALESCE(umpv.llm_base_url, ump.llm_base_url) AS llm_base_url,
  COALESCE(umpv.llm_api_key_ciphertext, ump.llm_api_key_ciphertext) AS llm_api_key_ciphertext,
  COALESCE(umpv.llm_wire_api, ump.llm_wire_api) AS llm_wire_api,
  ump.created_at,
  ump.updated_at
FROM user_model_profiles ump
LEFT JOIN user_model_providers umpv
  ON umpv.user_id = ump.user_id
 AND umpv.id = ump.provider_id
WHERE ump.user_id = $1 AND ump.id = $2
`, strings.TrimSpace(userID), strings.TrimSpace(profileID))
	profile, err := service.scanModelProfile(row, true)
	if err != nil {
		if coredb.IsNotFound(err) {
			return ModelProfile{}, ErrNotFound
		}
		return ModelProfile{}, err
	}
	return profile, nil
}

func (service *Service) DeleteModelProfileForUser(ctx context.Context, userID string, profileID string) error {
	profile, err := service.GetModelProfileForUser(ctx, userID, profileID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(profile.ProviderID) != "" {
		tx, err := service.db.BeginTx(ctx, coredb.TxOptions{})
		if err != nil {
			return fmt.Errorf("begin delete user model provider: %w", err)
		}
		defer tx.Rollback(ctx)
		if _, err := tx.Exec(ctx, `
DELETE FROM user_model_profiles
WHERE user_id = $1 AND provider_id = $2
`, strings.TrimSpace(userID), strings.TrimSpace(profile.ProviderID)); err != nil {
			return fmt.Errorf("delete user model provider profiles: %w", err)
		}
		result, err := tx.Exec(ctx, `
DELETE FROM user_model_providers
WHERE user_id = $1 AND id = $2
`, strings.TrimSpace(userID), strings.TrimSpace(profile.ProviderID))
		if err != nil {
			return fmt.Errorf("delete user model provider: %w", err)
		}
		if result.RowsAffected() == 0 {
			return ErrNotFound
		}
		return tx.Commit(ctx)
	}
	result, err := service.db.Exec(ctx, `
DELETE FROM user_model_profiles
WHERE user_id = $1 AND id = $2
`, strings.TrimSpace(userID), strings.TrimSpace(profileID))
	if err != nil {
		return fmt.Errorf("delete user model profile: %w", err)
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (service *Service) UpdateModelProfileForUser(ctx context.Context, userID string, profileID string, params UpdateModelProfileParams) ([]ModelProfile, error) {
	profile, err := service.GetModelProfileForUser(ctx, userID, profileID)
	if err != nil {
		return nil, err
	}
	providerID := strings.TrimSpace(profile.ProviderID)
	if providerID == "" {
		return nil, storeerr.BadRequest("model profile is not provider-backed")
	}
	userID = strings.TrimSpace(userID)
	displayName := strings.TrimSpace(params.DisplayName)
	if displayName == "" {
		return nil, storeerr.BadRequest("displayName is required")
	}
	models := normalizeModelProfileModels(params.Models)
	if len(models) == 0 {
		models = normalizeModelProfileModels([]string{params.Model})
	}
	if len(models) == 0 {
		return nil, storeerr.BadRequest("model is required")
	}
	now := time.Now().UTC()
	tx, err := service.db.BeginTx(ctx, coredb.TxOptions{})
	if err != nil {
		return nil, fmt.Errorf("begin update model provider: %w", err)
	}
	defer tx.Rollback(ctx)

	args := []any{
		userID,
		providerID,
		displayName,
		strings.TrimSpace(params.BaseURL),
		strings.TrimSpace(params.WireAPI),
		now,
	}
	updateProviderSQL := `
UPDATE user_model_providers
SET display_name = $3,
    llm_base_url = $4,
    llm_wire_api = $5,
    updated_at = $6`
	if strings.TrimSpace(params.APIKey) != "" {
		apiKeyCiphertext, err := EncryptAgentCredential(service.credentialStorageKey, params.APIKey)
		if err != nil {
			return nil, err
		}
		updateProviderSQL += `,
    llm_api_key_ciphertext = $7`
		args = append(args, apiKeyCiphertext)
	}
	updateProviderSQL += `
WHERE user_id = $1 AND id = $2`
	result, err := tx.Exec(ctx, updateProviderSQL, args...)
	if err != nil {
		if coredb.IsUniqueViolation(err) {
			return nil, storeerr.Conflict("a model provider with that name already exists")
		}
		return nil, fmt.Errorf("update user model provider: %w", err)
	}
	if result.RowsAffected() == 0 {
		return nil, ErrNotFound
	}

	rows, err := tx.Query(ctx, `
SELECT id, model
FROM user_model_profiles
WHERE user_id = $1 AND provider_id = $2
ORDER BY created_at ASC, id ASC
`, userID, providerID)
	if err != nil {
		return nil, fmt.Errorf("list existing model provider profiles: %w", err)
	}
	defer rows.Close()
	existingByModel := make(map[string]string)
	existingIDs := make([]string, 0)
	for rows.Next() {
		var existingID string
		var existingModel string
		if err := rows.Scan(&existingID, &existingModel); err != nil {
			return nil, fmt.Errorf("scan existing model provider profile: %w", err)
		}
		existingByModel[existingModel] = existingID
		existingIDs = append(existingIDs, existingID)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate existing model provider profiles: %w", err)
	}

	keepIDs := make(map[string]struct{}, len(models))
	insertValues := make([]string, 0)
	insertArgs := make([]any, 0)
	for _, model := range models {
		if existingID, ok := existingByModel[model]; ok {
			keepIDs[existingID] = struct{}{}
			continue
		}
		newProfileID, err := identifiers.NewEntityID("ump")
		if err != nil {
			return nil, fmt.Errorf("generate model profile id: %w", err)
		}
		base := len(insertArgs) + 1
		insertValues = append(insertValues, fmt.Sprintf("($%d, $%d, $%d, $%d, $%d, '', '', '', $%d, $%d)", base, base+1, base+2, base+3, base+4, base+5, base+5))
		insertArgs = append(insertArgs, newProfileID, providerID, userID, model, model, now)
		keepIDs[newProfileID] = struct{}{}
	}
	if len(insertValues) > 0 {
		if _, err := tx.Exec(ctx, fmt.Sprintf(`
INSERT INTO user_model_profiles (
  id, provider_id, user_id, display_name, model, llm_base_url, llm_api_key_ciphertext, llm_wire_api, created_at, updated_at
) VALUES %s
`, strings.Join(insertValues, ",\n")), insertArgs...); err != nil {
			return nil, fmt.Errorf("insert updated user model profiles: %w", err)
		}
	}
	deleteIDs := make([]string, 0)
	for _, existingID := range existingIDs {
		if _, ok := keepIDs[existingID]; ok {
			continue
		}
		deleteIDs = append(deleteIDs, existingID)
	}
	if len(deleteIDs) > 0 {
		deletePlaceholders := make([]string, 0, len(deleteIDs))
		deleteArgs := []any{userID}
		for _, existingID := range deleteIDs {
			deletePlaceholders = append(deletePlaceholders, fmt.Sprintf("$%d", len(deleteArgs)+1))
			deleteArgs = append(deleteArgs, existingID)
		}
		if _, err := tx.Exec(ctx, fmt.Sprintf(`
DELETE FROM user_model_profiles
WHERE user_id = $1 AND id IN (%s)
`, strings.Join(deletePlaceholders, ", ")), deleteArgs...); err != nil {
			return nil, fmt.Errorf("delete removed user model profiles: %w", err)
		}
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, fmt.Errorf("commit update model provider: %w", err)
	}
	return service.listModelProfilesForProvider(ctx, userID, providerID)
}

func (service *Service) listModelProfilesForProvider(ctx context.Context, userID string, providerID string) ([]ModelProfile, error) {
	rows, err := service.db.Query(ctx, `
SELECT
  ump.id,
  COALESCE(ump.provider_id, '') AS provider_id,
  ump.user_id,
  COALESCE(umpv.display_name, ump.display_name) AS display_name,
  ump.model,
  COALESCE(umpv.llm_base_url, ump.llm_base_url) AS llm_base_url,
  COALESCE(umpv.llm_api_key_ciphertext, ump.llm_api_key_ciphertext) AS llm_api_key_ciphertext,
  COALESCE(umpv.llm_wire_api, ump.llm_wire_api) AS llm_wire_api,
  ump.created_at,
  ump.updated_at
FROM user_model_profiles ump
LEFT JOIN user_model_providers umpv
  ON umpv.user_id = ump.user_id
 AND umpv.id = ump.provider_id
WHERE ump.user_id = $1 AND ump.provider_id = $2
ORDER BY ump.model ASC, ump.id ASC
`, strings.TrimSpace(userID), strings.TrimSpace(providerID))
	if err != nil {
		return nil, fmt.Errorf("list provider model profiles: %w", err)
	}
	defer rows.Close()
	profiles := make([]ModelProfile, 0)
	for rows.Next() {
		profile, err := service.scanModelProfile(rows, false)
		if err != nil {
			return nil, err
		}
		profiles = append(profiles, profile)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate provider model profiles: %w", err)
	}
	return profiles, nil
}

type modelProfileScanner interface {
	Scan(dest ...any) error
}

func (service *Service) scanModelProfile(scanner modelProfileScanner, includeAPIKey bool) (ModelProfile, error) {
	var profile ModelProfile
	var apiKeyCiphertext string
	if err := scanner.Scan(
		&profile.ID,
		&profile.ProviderID,
		&profile.UserID,
		&profile.DisplayName,
		&profile.Model,
		&profile.BaseURL,
		&apiKeyCiphertext,
		&profile.WireAPI,
		&profile.CreatedAt,
		&profile.UpdatedAt,
	); err != nil {
		return ModelProfile{}, fmt.Errorf("scan user model profile: %w", err)
	}
	profile.APIKeyConfigured = strings.TrimSpace(apiKeyCiphertext) != ""
	if profile.APIKeyConfigured && includeAPIKey {
		apiKey, err := DecryptAgentCredentialWithFallback(credentialStorageKeyCandidates(service.credentialStorageKey, service.legacyCredentialStorageKeys), apiKeyCiphertext)
		if err != nil {
			return ModelProfile{}, err
		}
		profile.APIKey = apiKey
	}
	return profile, nil
}

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