package oauth

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"xbot/crypto"
	"xbot/storage/sqlite"

	log "xbot/logger"
)

// TokenStorage defines the interface for storing OAuth tokens.
type TokenStorage interface {
	// GetToken retrieves a token for a provider and session.
	GetToken(ctx context.Context, provider, channel, chatID string) (*Token, error)

	// SetToken stores a token for a provider and session.
	SetToken(ctx context.Context, provider, channel, chatID string, token *Token) error

	// DeleteToken removes a token (e.g., on user logout).
	DeleteToken(ctx context.Context, provider, channel, chatID string) error

	// Close closes any underlying resources.
	Close() error
}

// SQLiteStorage implements TokenStorage using the shared SQLite database.
type SQLiteStorage struct {
	db *sqlite.DB
}

// NewSQLiteStorage creates a new SQLite-based token storage using the shared DB.
func NewSQLiteStorage(db *sqlite.DB) (*SQLiteStorage, error) {
	storage := &SQLiteStorage{db: db}
	if err := storage.initSchema(); err != nil {
		return nil, fmt.Errorf("initialize OAuth token schema: %w", err)
	}
	log.Info("OAuth token storage initialized (shared DB)")
	return storage, nil
}

// initSchema creates the oauth_tokens table if it doesn't exist.
func (s *SQLiteStorage) initSchema() error {
	query := `
	CREATE TABLE IF NOT EXISTS oauth_tokens (
		provider TEXT NOT NULL,
		channel TEXT NOT NULL,
		chat_id TEXT NOT NULL,
		access_token TEXT NOT NULL,
		refresh_token TEXT,
		expires_at INTEGER NOT NULL,
		scopes TEXT,
		raw TEXT,
		updated_at INTEGER NOT NULL,
		PRIMARY KEY (provider, channel, chat_id)
	);
	CREATE INDEX IF NOT EXISTS oauth_tokens_expires_at ON oauth_tokens(expires_at);
	`
	_, err := s.db.Conn().Exec(query)
	return err
}

// GetToken retrieves a token for a provider and session.
func (s *SQLiteStorage) GetToken(ctx context.Context, provider, channel, chatID string) (*Token, error) {
	query := `
	SELECT access_token, refresh_token, expires_at, scopes, raw
	FROM oauth_tokens
	WHERE provider = ? AND channel = ? AND chat_id = ?
	`

	var accessToken, refreshToken, scopesJSON, rawJSON string
	var expiresAt int64

	err := s.db.Conn().QueryRowContext(ctx, query, provider, channel, chatID).Scan(
		&accessToken, &refreshToken, &expiresAt, &scopesJSON, &rawJSON,
	)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, nil
		}
		return nil, fmt.Errorf("query token: %w", err)
	}

	// Token 未加密存储——DB 与服务同进程，加密只增加复杂度不增加安全性。
	// 保留 Decrypt 兼容路径：如果之前有加密数据，仍可正常解密。
	if accessToken != "" {
		if decrypted, err := crypto.Decrypt(accessToken); err == nil {
			accessToken = decrypted
		}
	}
	if refreshToken != "" {
		if decrypted, err := crypto.Decrypt(refreshToken); err == nil {
			refreshToken = decrypted
		}
	}

	var scopes []string
	if scopesJSON != "" {
		if err := json.Unmarshal([]byte(scopesJSON), &scopes); err != nil {
			log.WithError(err).Warn("Failed to parse scopes JSON")
		}
	}

	var raw map[string]any
	if rawJSON != "" {
		if err := json.Unmarshal([]byte(rawJSON), &raw); err != nil {
			log.WithError(err).Warn("Failed to parse raw JSON")
		}
	}

	return &Token{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		ExpiresAt:    time.Unix(expiresAt, 0),
		Scopes:       scopes,
		Raw:          raw,
	}, nil
}

// SetToken stores a token for a provider and session.
func (s *SQLiteStorage) SetToken(ctx context.Context, provider, channel, chatID string, token *Token) error {
	scopesJSON, _ := json.Marshal(token.Scopes)
	rawJSON, _ := json.Marshal(token.Raw)

	accessToken := token.AccessToken
	refreshToken := token.RefreshToken

	// Token 直接明文存储——DB 与服务同进程，加密无实际安全增益。
	// 保留 Encrypt 兼容：如果 crypto 已初始化密钥则加密（兼容旧数据读取），否则直接存储。
	if accessToken != "" {
		if encrypted, err := crypto.Encrypt(accessToken); err == nil {
			accessToken = encrypted
		}
		// 加密失败（未设置密钥等）→ 直接明文存储，不报错
	}
	if refreshToken != "" {
		if encrypted, err := crypto.Encrypt(refreshToken); err == nil {
			refreshToken = encrypted
		}
	}

	query := `
	REPLACE INTO oauth_tokens (provider, channel, chat_id, access_token, refresh_token, expires_at, scopes, raw, updated_at)
	VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`

	_, err := s.db.Conn().ExecContext(ctx, query,
		provider, channel, chatID,
		accessToken, refreshToken, token.ExpiresAt.Unix(),
		string(scopesJSON), string(rawJSON), time.Now().Unix(),
	)
	if err != nil {
		return fmt.Errorf("store token: %w", err)
	}

	log.WithFields(log.Fields{
		"provider": provider,
		"channel":  channel,
		"chat_id":  chatID,
	}).Debug("OAuth token stored")
	return nil
}

// DeleteToken removes a token for a provider and session.
func (s *SQLiteStorage) DeleteToken(ctx context.Context, provider, channel, chatID string) error {
	query := `DELETE FROM oauth_tokens WHERE provider = ? AND channel = ? AND chat_id = ?`
	_, err := s.db.Conn().ExecContext(ctx, query, provider, channel, chatID)
	if err != nil {
		return fmt.Errorf("delete token: %w", err)
	}
	return nil
}

// Close is a no-op since the shared DB is managed externally.
func (s *SQLiteStorage) Close() error {
	return nil
}

// CleanupExpiredTokens removes tokens that have expired.
func (s *SQLiteStorage) CleanupExpiredTokens(ctx context.Context, olderThan time.Duration) error {
	cutoff := time.Now().Add(-olderThan)
	result, err := s.db.Conn().ExecContext(ctx, `DELETE FROM oauth_tokens WHERE expires_at < ?`, cutoff.Unix())
	if err != nil {
		return fmt.Errorf("cleanup expired tokens: %w", err)
	}
	rows, _ := result.RowsAffected()
	if rows > 0 {
		log.WithField("count", rows).Info("Cleaned up expired OAuth tokens")
	}
	return nil
}
