<?php

declare(strict_types=1);

namespace App\Framework\OAuth\Providers;

use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;
use App\Framework\OAuth\OAuthClient;
use App\Framework\OAuth\OAuthProvider;
use App\Framework\OAuth\ValueObjects\OAuthToken;

/**
 * Tidal OAuth Provider
 *
 * Implements Tidal API OAuth 2.0 flow for pre-save campaigns
 * @see https://developer.tidal.com/documentation/api/api-overview
 */
final readonly class TidalProvider implements OAuthProvider
{
    private const AUTHORIZATION_URL = 'https://login.tidal.com/authorize';
    private const TOKEN_URL = 'https://auth.tidal.com/v1/oauth2/token';
    private const API_BASE_URL = 'https://api.tidal.com/v1';

    public function __construct(
        private OAuthClient $client,
        private string $clientId,
        private string $clientSecret,
        private string $redirectUri,
    ) {
        if (empty($clientId) || empty($clientSecret)) {
            throw FrameworkException::create(
                ErrorCode::CONFIG_MISSING,
                'Tidal OAuth credentials not configured'
            )->withData([
                'client_id_set' => ! empty($clientId),
                'client_secret_set' => ! empty($clientSecret),
            ]);
        }
    }

    public function getName(): string
    {
        return 'tidal';
    }

    /**
     * Get default scopes for Tidal
     *
     * Scopes for pre-save campaigns:
     * - r_usr: Read user data
     * - w_usr: Write user data (add to favorites)
     *
     * @return array<string>
     */
    public function getDefaultScopes(): array
    {
        return [
            'r_usr',
            'w_usr',
        ];
    }

    public function getAuthorizationUrl(array $options = []): string
    {
        $scopes = $options['scope'] ?? $this->getDefaultScopes();
        $state = $options['state'] ?? bin2hex(random_bytes(16));

        $params = http_build_query([
            'client_id' => $this->clientId,
            'response_type' => 'code',
            'redirect_uri' => $this->redirectUri,
            'scope' => is_array($scopes) ? implode(' ', $scopes) : $scopes,
            'state' => $state,
        ]);

        return self::AUTHORIZATION_URL . '?' . $params;
    }

    public function getAccessToken(string $code, ?string $state = null): OAuthToken
    {
        $response = $this->client->post(self::TOKEN_URL, [
            'grant_type' => 'authorization_code',
            'code' => $code,
            'redirect_uri' => $this->redirectUri,
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
        ]);

        if (! isset($response['access_token'])) {
            throw FrameworkException::create(
                ErrorCode::AUTH_TOKEN_EXCHANGE_FAILED,
                'Failed to exchange code for access token'
            )->withData([
                'provider' => 'tidal',
                'error' => $response['error'] ?? 'unknown',
                'error_description' => $response['error_description'] ?? null,
            ]);
        }

        return OAuthToken::fromProviderResponse($response);
    }

    public function refreshToken(OAuthToken $token): OAuthToken
    {
        if (! $token->canRefresh()) {
            throw FrameworkException::create(
                ErrorCode::AUTH_TOKEN_REFRESH_FAILED,
                'Token cannot be refreshed (no refresh token)'
            )->withData([
                'provider' => 'tidal',
            ]);
        }

        $response = $this->client->post(self::TOKEN_URL, [
            'grant_type' => 'refresh_token',
            'refresh_token' => $token->refreshToken->toString(),
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
        ]);

        if (! isset($response['access_token'])) {
            throw FrameworkException::create(
                ErrorCode::AUTH_TOKEN_REFRESH_FAILED,
                'Failed to refresh access token'
            )->withData([
                'provider' => 'tidal',
                'error' => $response['error'] ?? 'unknown',
            ]);
        }

        $newToken = OAuthToken::fromProviderResponse($response);

        // If no new refresh token, keep the old one
        if ($newToken->refreshToken === null) {
            return $newToken->withRefreshedAccessToken(
                $newToken->accessToken,
                $token->refreshToken
            );
        }

        return $newToken;
    }

    public function revokeToken(OAuthToken $token): bool
    {
        // Tidal token revocation would be implemented here if available
        return true;
    }

    /**
     * Get user profile from Tidal
     *
     * @return array<string, mixed>
     */
    public function getUserProfile(OAuthToken $token): array
    {
        try {
            return $this->client->get(self::API_BASE_URL . '/users/me', $token);
        } catch (\Exception $e) {
            throw FrameworkException::create(
                ErrorCode::EXTERNAL_API_ERROR,
                'Failed to fetch Tidal user profile'
            )->withData([
                'provider' => 'tidal',
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Add tracks to user's favorites (for pre-save campaigns)
     *
     * @param array<string> $trackIds Tidal track IDs
     */
    public function addTracksToFavorites(OAuthToken $token, array $trackIds): bool
    {
        if (empty($trackIds)) {
            return true;
        }

        // Get user ID for favorites endpoint
        $profile = $this->getUserProfile($token);
        $userId = $profile['userId'] ?? $profile['id'] ?? null;

        if (! $userId) {
            throw FrameworkException::create(
                ErrorCode::EXTERNAL_API_ERROR,
                'Could not determine Tidal user ID'
            );
        }

        // Add tracks to favorites
        foreach ($trackIds as $trackId) {
            $this->client->put(
                self::API_BASE_URL . "/users/{$userId}/favorites/tracks",
                $token,
                ['trackIds' => [$trackId]]
            );
        }

        return true;
    }

    /**
     * Extract track ID from Tidal URL
     *
     * Examples:
     * - https://tidal.com/browse/track/12345678
     * - https://listen.tidal.com/track/12345678
     */
    public function extractTrackId(string $trackUrl): string
    {
        if (preg_match('/track\/(\d+)/', $trackUrl, $matches)) {
            return $matches[1];
        }

        throw new \InvalidArgumentException('Invalid Tidal track URL: ' . $trackUrl);
    }
}
