package organization

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"database/sql"
	"encoding/base64"
	"fmt"
	"strings"
	"time"

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

type organizationProviderTokenRecord struct {
	ID                     string
	OrganizationID         string
	OrganizationProviderID string
	Label                  string
	TokenCiphertext        string
	SecretJSONCiphertext   string
	TokenHint              string
	ConfigJSON             []byte
	Status                 string
	LastUsedAt             sql.NullTime
	LastErrorAt            sql.NullTime
	LastErrorCode          string
	CreatedByUserID        string
	CreatedAt              time.Time
	UpdatedAt              time.Time
}

func (txs TxStore) hasOrganizationProviderTokenStorage() bool {
	return len(txs.feishuCredentialEncryptionKey) == 32
}

func (txs TxStore) ListOrganizationProviderTokensForUser(ctx context.Context, userID string, organizationID string, providerID string) ([]OrganizationProviderTokenView, error) {
	if _, err := txs.requireActiveOrganizationAdmin(ctx, txs.db, userID, organizationID); err != nil {
		return nil, err
	}
	if _, err := txs.loadOrganizationProvider(ctx, txs.db, organizationID, providerID); err != nil {
		return nil, err
	}
	rows, err := txs.db.Query(ctx, `
SELECT id, organization_id, organization_provider_id, label, token_hint, config_json, status, last_used_at, last_error_at, last_error_code, created_at, updated_at
FROM organization_provider_tokens
WHERE organization_id = $1
  AND organization_provider_id = $2
ORDER BY created_at ASC, id ASC
`, organizationID, providerID)
	if err != nil {
		return nil, fmt.Errorf("list organization provider tokens: %w", err)
	}
	defer rows.Close()

	items := make([]OrganizationProviderTokenView, 0)
	for rows.Next() {
		item, err := scanOrganizationProviderTokenView(rows)
		if err != nil {
			return nil, fmt.Errorf("scan organization provider token: %w", err)
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate organization provider tokens: %w", err)
	}
	return items, nil
}

func (txs TxStore) CreateOrganizationProviderTokenForUser(ctx context.Context, userID string, organizationID string, providerID string, params CreateOrganizationProviderTokenParams) (OrganizationProviderTokenView, error) {
	if _, err := txs.requireActiveOrganizationAdmin(ctx, txs.db, userID, organizationID); err != nil {
		return OrganizationProviderTokenView{}, err
	}
	if !txs.hasOrganizationProviderTokenStorage() {
		return OrganizationProviderTokenView{}, badRequest("organization provider token storage is not configured on server")
	}

	provider, err := txs.loadOrganizationProvider(ctx, txs.db, organizationID, providerID)
	if err != nil {
		return OrganizationProviderTokenView{}, err
	}

	label := strings.TrimSpace(params.Label)
	if label == "" {
		return OrganizationProviderTokenView{}, badRequest("label is required")
	}

	config := normalizeOrganizationProviderTokenConfig(params.Config)
	secret := normalizeOrganizationProviderTokenMaterial(params.Secret)
	legacyToken := strings.TrimSpace(params.Token)

	switch strings.TrimSpace(provider.DriverKind) {
	case "codex":
		if organizationProviderTokenMaterialEmpty(secret) && legacyToken != "" {
			if strings.EqualFold(strings.TrimSpace(config.Mode), "chatgpt") {
				secret.AuthToken = legacyToken
			} else {
				secret.APIKey = legacyToken
			}
		}
		if organizationProviderTokenMaterialEmpty(secret) {
			return OrganizationProviderTokenView{}, badRequest("provider credential secret is required")
		}
		if strings.TrimSpace(config.Role) == "" {
			config.Role = "primary"
		}
	default:
		token := firstNonEmpty(secret.Token, legacyToken)
		if token == "" {
			return OrganizationProviderTokenView{}, badRequest("token is required")
		}
		secret.Token = token
		legacyToken = token
	}

	tokenID, err := identifiers.NewEntityID("optk")
	if err != nil {
		return OrganizationProviderTokenView{}, fmt.Errorf("generate organization provider token id: %w", err)
	}

	configJSON, err := encodeOrganizationProviderTokenConfig(config)
	if err != nil {
		return OrganizationProviderTokenView{}, err
	}

	tokenCiphertext := ""
	if trimmed := firstNonEmpty(legacyToken, secret.Token); trimmed != "" {
		tokenCiphertext, err = encryptOrganizationProviderToken(txs.feishuCredentialEncryptionKey, trimmed)
		if err != nil {
			return OrganizationProviderTokenView{}, fmt.Errorf("encrypt organization provider token: %w", err)
		}
	}

	secretJSONCiphertext := ""
	if !organizationProviderTokenMaterialEmpty(secret) {
		secretPayload, secretErr := encodeOrganizationProviderTokenMaterial(secret)
		if secretErr != nil {
			return OrganizationProviderTokenView{}, secretErr
		}
		secretJSONCiphertext, err = encryptOrganizationProviderToken(txs.feishuCredentialEncryptionKey, secretPayload)
		if err != nil {
			return OrganizationProviderTokenView{}, fmt.Errorf("encrypt organization provider token secret: %w", err)
		}
	}

	now := time.Now().UTC()
	if _, err := txs.db.Exec(ctx, `
INSERT INTO organization_provider_tokens (
    id,
    organization_id,
    organization_provider_id,
    label,
    token_ciphertext,
    secret_json_ciphertext,
    token_hint,
    config_json,
    status,
    created_by_user_id,
    created_at,
    updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, CAST(NULLIF($8, '') AS JSON), $9, $10, $11, $11)
`, tokenID, organizationID, providerID, label, tokenCiphertext, secretJSONCiphertext, organizationProviderTokenHint(secret, legacyToken), configJSON, organizationProviderTokenStatusActive, userID, now); err != nil {
		return OrganizationProviderTokenView{}, fmt.Errorf("insert organization provider token: %w", err)
	}
	return txs.loadOrganizationProviderToken(ctx, txs.db, organizationID, providerID, tokenID)
}

func (txs TxStore) DeleteOrganizationProviderTokenForUser(ctx context.Context, userID string, organizationID string, providerID string, tokenID string) error {
	if _, err := txs.requireActiveOrganizationAdmin(ctx, txs.db, userID, organizationID); err != nil {
		return err
	}
	if _, err := txs.loadOrganizationProvider(ctx, txs.db, organizationID, providerID); err != nil {
		return err
	}
	result, err := txs.db.Exec(ctx, `
DELETE FROM organization_provider_tokens
WHERE organization_id = $1
  AND organization_provider_id = $2
  AND id = $3
`, organizationID, providerID, tokenID)
	if err != nil {
		return fmt.Errorf("delete organization provider token: %w", err)
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (txs TxStore) LoadActiveOrganizationProviderTokens(ctx context.Context, organizationID string, providerID string) ([]OrganizationProviderTokenSecret, error) {
	rows, err := txs.db.Query(ctx, `
SELECT id, organization_id, organization_provider_id, label, token_ciphertext, COALESCE(secret_json_ciphertext, '') AS secret_json_ciphertext, token_hint, config_json, status, last_used_at, last_error_at, last_error_code, created_by_user_id, created_at, updated_at
FROM organization_provider_tokens
WHERE organization_id = $1
  AND organization_provider_id = $2
  AND status = $3
ORDER BY created_at ASC, id ASC
`, organizationID, providerID, organizationProviderTokenStatusActive)
	if err != nil {
		return nil, fmt.Errorf("list active organization provider tokens: %w", err)
	}
	defer rows.Close()

	items := make([]OrganizationProviderTokenSecret, 0)
	for rows.Next() {
		record, err := scanOrganizationProviderTokenRecord(rows)
		if err != nil {
			return nil, fmt.Errorf("scan active organization provider token: %w", err)
		}
		legacyToken := ""
		if strings.TrimSpace(record.TokenCiphertext) != "" {
			legacyToken, err = decryptOrganizationProviderToken(txs.feishuCredentialEncryptionKey, record.TokenCiphertext)
			if err != nil {
				return nil, fmt.Errorf("decrypt organization provider token %s: %w", record.ID, err)
			}
		}
		secret := OrganizationProviderTokenMaterial{}
		if strings.TrimSpace(record.SecretJSONCiphertext) != "" {
			plaintext, err := decryptOrganizationProviderToken(txs.feishuCredentialEncryptionKey, record.SecretJSONCiphertext)
			if err != nil {
				return nil, fmt.Errorf("decrypt organization provider token secret %s: %w", record.ID, err)
			}
			secret, err = decodeOrganizationProviderTokenMaterial(plaintext)
			if err != nil {
				return nil, fmt.Errorf("decode organization provider token secret %s: %w", record.ID, err)
			}
		}
		if strings.TrimSpace(secret.Token) == "" && strings.TrimSpace(legacyToken) != "" {
			secret.Token = legacyToken
		}
		config, err := decodeOrganizationProviderTokenConfig(record.ConfigJSON)
		if err != nil {
			return nil, fmt.Errorf("decode organization provider token config %s: %w", record.ID, err)
		}
		items = append(items, OrganizationProviderTokenSecret{
			ID:        record.ID,
			Label:     record.Label,
			Token:     firstNonEmpty(secret.Token, legacyToken),
			Hint:      record.TokenHint,
			UpdatedAt: record.UpdatedAt,
			Config:    config,
			Secret:    secret,
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate active organization provider tokens: %w", err)
	}
	return items, nil
}

func (txs TxStore) UpdateOrganizationProviderTokenSecret(ctx context.Context, organizationID string, providerID string, tokenID string, params UpdateOrganizationProviderTokenSecretParams) error {
	if !txs.hasOrganizationProviderTokenStorage() {
		return badRequest("organization provider token storage is not configured on server")
	}
	secret := normalizeOrganizationProviderTokenMaterial(params.Secret)
	if organizationProviderTokenMaterialEmpty(secret) {
		return badRequest("provider credential secret is required")
	}
	secretPayload, err := encodeOrganizationProviderTokenMaterial(secret)
	if err != nil {
		return err
	}
	secretCiphertext, err := encryptOrganizationProviderToken(txs.feishuCredentialEncryptionKey, secretPayload)
	if err != nil {
		return fmt.Errorf("encrypt organization provider token secret: %w", err)
	}
	tokenCiphertext := ""
	if trimmed := strings.TrimSpace(secret.Token); trimmed != "" {
		tokenCiphertext, err = encryptOrganizationProviderToken(txs.feishuCredentialEncryptionKey, trimmed)
		if err != nil {
			return fmt.Errorf("encrypt organization provider token: %w", err)
		}
	}
	now := time.Now().UTC()
	result, err := txs.db.Exec(ctx, `
UPDATE organization_provider_tokens
SET token_ciphertext = $4,
    secret_json_ciphertext = $5,
    token_hint = $6,
    updated_at = $7
WHERE organization_id = $1
  AND organization_provider_id = $2
  AND id = $3
`, organizationID, providerID, tokenID, tokenCiphertext, secretCiphertext, organizationProviderTokenHint(secret, secret.Token), now)
	if err != nil {
		return fmt.Errorf("update organization provider token secret: %w", err)
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (txs TxStore) loadOrganizationProviderToken(ctx context.Context, queryer queryRower, organizationID string, providerID string, tokenID string) (OrganizationProviderTokenView, error) {
	row := queryer.QueryRow(ctx, `
SELECT id, organization_id, organization_provider_id, label, token_hint, config_json, status, last_used_at, last_error_at, last_error_code, created_at, updated_at
FROM organization_provider_tokens
WHERE organization_id = $1
  AND organization_provider_id = $2
  AND id = $3
`, organizationID, providerID, tokenID)
	item, err := scanOrganizationProviderTokenView(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return OrganizationProviderTokenView{}, ErrNotFound
		}
		return OrganizationProviderTokenView{}, fmt.Errorf("load organization provider token: %w", err)
	}
	return item, nil
}

func scanOrganizationProviderTokenView(scanner interface{ Scan(...any) error }) (OrganizationProviderTokenView, error) {
	var item OrganizationProviderTokenView
	var configRaw []byte
	var lastUsedAt sql.NullTime
	var lastErrorAt sql.NullTime
	if err := scanner.Scan(
		&item.ID,
		&item.OrganizationID,
		&item.OrganizationProviderID,
		&item.Label,
		&item.TokenHint,
		&configRaw,
		&item.Status,
		&lastUsedAt,
		&lastErrorAt,
		&item.LastErrorCode,
		&item.CreatedAt,
		&item.UpdatedAt,
	); err != nil {
		return OrganizationProviderTokenView{}, err
	}
	config, err := decodeOrganizationProviderTokenConfig(configRaw)
	if err != nil {
		return OrganizationProviderTokenView{}, err
	}
	if lastUsedAt.Valid {
		t := lastUsedAt.Time.UTC()
		item.LastUsedAt = &t
	}
	if lastErrorAt.Valid {
		t := lastErrorAt.Time.UTC()
		item.LastErrorAt = &t
	}
	item.Config = config
	return item, nil
}

func scanOrganizationProviderTokenRecord(scanner interface{ Scan(...any) error }) (organizationProviderTokenRecord, error) {
	var item organizationProviderTokenRecord
	if err := scanner.Scan(
		&item.ID,
		&item.OrganizationID,
		&item.OrganizationProviderID,
		&item.Label,
		&item.TokenCiphertext,
		&item.SecretJSONCiphertext,
		&item.TokenHint,
		&item.ConfigJSON,
		&item.Status,
		&item.LastUsedAt,
		&item.LastErrorAt,
		&item.LastErrorCode,
		&item.CreatedByUserID,
		&item.CreatedAt,
		&item.UpdatedAt,
	); err != nil {
		return organizationProviderTokenRecord{}, err
	}
	return item, nil
}

func organizationProviderTokenHint(secret OrganizationProviderTokenMaterial, fallback string) string {
	switch {
	case strings.TrimSpace(secret.Token) != "":
		return tokenHint(secret.Token)
	case strings.TrimSpace(secret.APIKey) != "":
		return tokenHint(secret.APIKey)
	case strings.TrimSpace(secret.AuthToken) != "":
		return tokenHint(secret.AuthToken)
	case strings.TrimSpace(secret.AuthJSONContent) != "":
		var payload map[string]any
		if err := model.UnmarshalJSONString("provider token auth json", secret.AuthJSONContent, &payload); err == nil {
			if accessToken := strings.TrimSpace(fmt.Sprint(payload["access_token"])); accessToken != "" && accessToken != "<nil>" {
				return tokenHint(accessToken)
			}
			if refreshToken := strings.TrimSpace(fmt.Sprint(payload["refresh_token"])); refreshToken != "" && refreshToken != "<nil>" {
				return tokenHint(refreshToken)
			}
		}
		return tokenHint(secret.AuthJSONContent)
	default:
		return tokenHint(fallback)
	}
}

func tokenHint(token string) string {
	trimmed := strings.TrimSpace(token)
	if trimmed == "" {
		return ""
	}
	if len(trimmed) <= 4 {
		return trimmed
	}
	return trimmed[len(trimmed)-4:]
}

func normalizeOrganizationProviderTokenConfig(config OrganizationProviderTokenConfig) OrganizationProviderTokenConfig {
	config.Role = strings.TrimSpace(config.Role)
	config.BaseURL = strings.TrimSpace(config.BaseURL)
	config.Mode = strings.TrimSpace(config.Mode)
	config.BackendName = strings.TrimSpace(config.BackendName)
	config.AllowedAPIKeys = normalizeOrganizationProviderTokenStringList(config.AllowedAPIKeys)
	config.CustomPrompt = strings.TrimSpace(config.CustomPrompt)
	config.CustomPromptModels = normalizeOrganizationProviderTokenStringList(config.CustomPromptModels)
	return config
}

func organizationProviderTokenConfigEmpty(config OrganizationProviderTokenConfig) bool {
	return strings.TrimSpace(config.Role) == "" &&
		strings.TrimSpace(config.BaseURL) == "" &&
		strings.TrimSpace(config.Mode) == "" &&
		strings.TrimSpace(config.BackendName) == "" &&
		config.BackendID == 0 &&
		config.Active == nil &&
		config.Weight == 0 &&
		config.WeightDiffThreshold == 0 &&
		!config.AdaptiveWeights &&
		!config.RequiresOAuthBeta &&
		len(config.AllowedAPIKeys) == 0 &&
		strings.TrimSpace(config.CustomPrompt) == "" &&
		len(config.CustomPromptModels) == 0
}

func encodeOrganizationProviderTokenConfig(config OrganizationProviderTokenConfig) (string, error) {
	normalized := normalizeOrganizationProviderTokenConfig(config)
	if organizationProviderTokenConfigEmpty(normalized) {
		return "", nil
	}
	payload := model.ProviderTokenConfig{
		Role:                normalized.Role,
		BaseURL:             normalized.BaseURL,
		Mode:                normalized.Mode,
		BackendName:         normalized.BackendName,
		BackendID:           normalized.BackendID,
		Active:              normalized.Active,
		Weight:              normalized.Weight,
		WeightDiffThreshold: normalized.WeightDiffThreshold,
		AdaptiveWeights:     normalized.AdaptiveWeights,
		RequiresOAuthBeta:   normalized.RequiresOAuthBeta,
		AllowedAPIKeys:      normalized.AllowedAPIKeys,
		CustomPrompt:        normalized.CustomPrompt,
		CustomPromptModels:  normalized.CustomPromptModels,
	}
	return model.MarshalJSONString("provider token config", payload)
}

func decodeOrganizationProviderTokenConfig(data []byte) (OrganizationProviderTokenConfig, error) {
	var payload model.ProviderTokenConfig
	if err := model.UnmarshalJSONObject("provider token config", data, &payload); err != nil {
		return OrganizationProviderTokenConfig{}, err
	}
	config := OrganizationProviderTokenConfig{
		Role:                payload.Role,
		BaseURL:             payload.BaseURL,
		Mode:                payload.Mode,
		BackendName:         payload.BackendName,
		BackendID:           payload.BackendID,
		Active:              payload.Active,
		Weight:              payload.Weight,
		WeightDiffThreshold: payload.WeightDiffThreshold,
		AdaptiveWeights:     payload.AdaptiveWeights,
		RequiresOAuthBeta:   payload.RequiresOAuthBeta,
		AllowedAPIKeys:      payload.AllowedAPIKeys,
		CustomPrompt:        payload.CustomPrompt,
		CustomPromptModels:  payload.CustomPromptModels,
	}
	return normalizeOrganizationProviderTokenConfig(config), nil
}

func normalizeOrganizationProviderTokenMaterial(secret OrganizationProviderTokenMaterial) OrganizationProviderTokenMaterial {
	secret.Token = strings.TrimSpace(secret.Token)
	secret.APIKey = strings.TrimSpace(secret.APIKey)
	secret.AuthToken = strings.TrimSpace(secret.AuthToken)
	secret.AuthJSONContent = strings.TrimSpace(secret.AuthJSONContent)
	return secret
}

func organizationProviderTokenMaterialEmpty(secret OrganizationProviderTokenMaterial) bool {
	return normalizeOrganizationProviderTokenMaterial(secret) == (OrganizationProviderTokenMaterial{})
}

func encodeOrganizationProviderTokenMaterial(secret OrganizationProviderTokenMaterial) (string, error) {
	normalized := normalizeOrganizationProviderTokenMaterial(secret)
	payload := model.ProviderTokenMaterial{
		Token:           normalized.Token,
		APIKey:          normalized.APIKey,
		AuthToken:       normalized.AuthToken,
		AuthJSONContent: normalized.AuthJSONContent,
	}
	return model.MarshalJSONString("provider token material", payload)
}

func decodeOrganizationProviderTokenMaterial(data string) (OrganizationProviderTokenMaterial, error) {
	var payload model.ProviderTokenMaterial
	if err := model.UnmarshalJSONString("provider token material", data, &payload); err != nil {
		return OrganizationProviderTokenMaterial{}, err
	}
	secret := OrganizationProviderTokenMaterial{
		Token:           payload.Token,
		APIKey:          payload.APIKey,
		AuthToken:       payload.AuthToken,
		AuthJSONContent: payload.AuthJSONContent,
	}
	return normalizeOrganizationProviderTokenMaterial(secret), nil
}

func normalizeOrganizationProviderTokenStringList(items []string) []string {
	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 normalized
}

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

func encryptOrganizationProviderToken(key []byte, plaintext string) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("create organization provider token cipher: %w", err)
	}
	aead, err := cipher.NewGCM(block)
	if err != nil {
		return "", fmt.Errorf("create organization provider token gcm: %w", err)
	}
	nonce := make([]byte, aead.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", fmt.Errorf("generate organization provider token nonce: %w", err)
	}
	sealed := aead.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.RawStdEncoding.EncodeToString(sealed), nil
}

func decryptOrganizationProviderToken(key []byte, ciphertext string) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("create organization provider token cipher: %w", err)
	}
	aead, err := cipher.NewGCM(block)
	if err != nil {
		return "", fmt.Errorf("create organization provider token gcm: %w", err)
	}
	payload, err := base64.RawStdEncoding.DecodeString(strings.TrimSpace(ciphertext))
	if err != nil {
		return "", fmt.Errorf("decode organization provider token ciphertext: %w", err)
	}
	if len(payload) < aead.NonceSize() {
		return "", fmt.Errorf("organization provider token ciphertext is too short")
	}
	nonce := payload[:aead.NonceSize()]
	encrypted := payload[aead.NonceSize():]
	plaintext, err := aead.Open(nil, nonce, encrypted, nil)
	if err != nil {
		return "", fmt.Errorf("decrypt organization provider token ciphertext: %w", err)
	}
	return string(plaintext), nil
}
