<?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;

/**
 * Apple Music OAuth Provider
 *
 * Implements Apple Music API OAuth 2.0 flow (MusicKit)
 * @see https://developer.apple.com/documentation/applemusicapi
 */
final readonly class AppleMusicProvider implements OAuthProvider
{
    private const AUTHORIZATION_URL = 'https://appleid.apple.com/auth/authorize';
    private const TOKEN_URL = 'https://appleid.apple.com/auth/token';
    private const API_BASE_URL = 'https://api.music.apple.com/v1';

    public function __construct(
        private OAuthClient $client,
        private string $clientId,
        private string $teamId,
        private string $keyId,
        private string $privateKey,
        private string $redirectUri,
    ) {
        if (empty($clientId) || empty($teamId) || empty($keyId) || empty($privateKey)) {
            throw FrameworkException::create(
                ErrorCode::CONFIG_MISSING,
                'Apple Music OAuth credentials not configured'
            )->withData([
                'client_id_set' => ! empty($clientId),
                'team_id_set' => ! empty($teamId),
                'key_id_set' => ! empty($keyId),
                'private_key_set' => ! empty($privateKey),
            ]);
        }
    }

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

    /**
     * Get default scopes for Apple Music
     *
     * @return array<string>
     */
    public function getDefaultScopes(): array
    {
        return [
            'music-user-library-modify',
            'music-user-library-read',
        ];
    }

    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',
            'response_mode' => 'form_post',
            '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
    {
        // Apple Music uses JWT for authentication
        $clientSecret = $this->generateClientSecret();

        $response = $this->client->post(self::TOKEN_URL, [
            'grant_type' => 'authorization_code',
            'code' => $code,
            'redirect_uri' => $this->redirectUri,
            'client_id' => $this->clientId,
            'client_secret' => $clientSecret,
        ]);

        if (! isset($response['access_token'])) {
            throw FrameworkException::create(
                ErrorCode::AUTH_TOKEN_EXCHANGE_FAILED,
                'Failed to exchange code for access token'
            )->withData([
                'provider' => 'apple_music',
                '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' => 'apple_music',
            ]);
        }

        $clientSecret = $this->generateClientSecret();

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

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

        return OAuthToken::fromProviderResponse($response);
    }

    public function revokeToken(OAuthToken $token): bool
    {
        // Apple Music token revocation
        $clientSecret = $this->generateClientSecret();

        $this->client->post('https://appleid.apple.com/auth/revoke', [
            'token' => $token->accessToken->toString(),
            'client_id' => $this->clientId,
            'client_secret' => $clientSecret,
            'token_type_hint' => 'access_token',
        ]);

        return true;
    }

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

    /**
     * Get user storefront (required for API calls)
     *
     * @return array<string, mixed>
     */
    public function getUserStorefront(OAuthToken $token): array
    {
        try {
            return $this->client->get(self::API_BASE_URL . '/me/storefront', $token);
        } catch (\Exception $e) {
            throw FrameworkException::create(
                ErrorCode::EXTERNAL_API_ERROR,
                'Failed to fetch Apple Music user storefront'
            )->withData([
                'provider' => 'apple_music',
                'error' => $e->getMessage(),
            ]);
        }
    }

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

        // Apple Music allows multiple tracks in one request
        $this->client->post(
            self::API_BASE_URL . '/me/library',
            $token,
            [
                'ids' => $trackIds,
                'type' => 'songs',
            ]
        );

        return true;
    }

    /**
     * Extract track ID from Apple Music URL
     *
     * Examples:
     * - https://music.apple.com/us/album/song-name/1234567890?i=9876543210
     * - https://music.apple.com/album/1234567890
     */
    public function extractTrackId(string $trackUrl): string
    {
        // Try to extract track ID from i= parameter
        if (preg_match('/[?&]i=(\d+)/', $trackUrl, $matches)) {
            return $matches[1];
        }

        // Try to extract album ID
        if (preg_match('/album\/[^\/]+\/(\d+)/', $trackUrl, $matches)) {
            return $matches[1];
        }

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

    /**
     * Generate JWT client secret for Apple Music API
     *
     * Apple requires a JWT signed with the private key
     */
    private function generateClientSecret(): string
    {
        $header = [
            'alg' => 'ES256',
            'kid' => $this->keyId,
        ];

        $now = time();
        $payload = [
            'iss' => $this->teamId,
            'iat' => $now,
            'exp' => $now + 3600, // 1 hour
            'aud' => 'https://appleid.apple.com',
            'sub' => $this->clientId,
        ];

        $headerEncoded = $this->base64UrlEncode(json_encode($header));
        $payloadEncoded = $this->base64UrlEncode(json_encode($payload));
        $signature = '';

        $dataToSign = $headerEncoded . '.' . $payloadEncoded;

        // Sign with ES256 (ECDSA using P-256 and SHA-256)
        openssl_sign(
            $dataToSign,
            $signature,
            $this->privateKey,
            OPENSSL_ALGO_SHA256
        );

        $signatureEncoded = $this->base64UrlEncode($signature);

        return $dataToSign . '.' . $signatureEncoded;
    }

    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
}
