<?php

namespace App\Domain\Manga\Infrastructure\Client;

use App\Domain\Manga\Domain\Contract\Client\MangadexClientInterface;
use App\Domain\Manga\Domain\Exception\MangadexApiException;
use App\Domain\Manga\Domain\Exception\MangadexAuthenticationException;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class MangadexClient implements MangadexClientInterface
{
    private const API_URL = 'https://api.mangadex.org';
    private const AUTH_URL = 'https://auth.mangadex.org/realms/mangadex/protocol/openid-connect/token';
    private const EXCLUDED_TAGS = ['0234a31e-a729-4e28-9d6a-3f87c4966b9e' , 'b13b2a48-c720-44a9-9c77-39c9979373fb', '891cf039-b895-47f0-9229-bef4c96eccd4'];

    private ?string $accessToken = null;
    private ?string $refreshToken = null;

    public function __construct(
        private HttpClientInterface $client,
        private string $clientId,
        private string $clientSecret,
        private string $username,
        private string $password
    ) {
    }

    public function authenticate(): void
    {
        try {
            $response = $this->client->request('POST', self::AUTH_URL, [
                'body' => [
                    'grant_type' => 'password',
                    'username' => $this->username,
                    'password' => $this->password,
                    'client_id' => $this->clientId,
                    'client_secret' => $this->clientSecret,
                ]
            ]);

            $data = $response->toArray();

            if (!isset($data['access_token'], $data['refresh_token'])) {
                throw new MangadexAuthenticationException('Invalid authentication response from Mangadex');
            }

            $this->accessToken = $data['access_token'];
            $this->refreshToken = $data['refresh_token'];
        } catch (\Exception $e) {
            throw new MangadexAuthenticationException(
                'Failed to authenticate with Mangadex: ' . $e->getMessage(),
                $e
            );
        }
    }

    public function refreshToken(): void
    {
        if (!$this->refreshToken) {
            throw new MangadexAuthenticationException('No refresh token available');
        }

        try {
            $response = $this->client->request('POST', self::AUTH_URL, [
                'body' => [
                    'grant_type' => 'refresh_token',
                    'refresh_token' => $this->refreshToken,
                    'client_id' => $this->clientId,
                    'client_secret' => $this->clientSecret,
                ]
            ]);

            $data = $response->toArray();

            if (!isset($data['access_token'])) {
                throw new MangadexAuthenticationException('Invalid refresh token response from Mangadex');
            }

            $this->accessToken = $data['access_token'];
            $this->refreshToken = $data['refresh_token'] ?? $this->refreshToken;
        } catch (\Exception $e) {
            throw new MangadexAuthenticationException(
                'Failed to refresh token: ' . $e->getMessage(),
                $e
            );
        }
    }

    public function searchManga(string $title): array
    {
        return $this->get('/manga', [
            'title' => $title,
            'contentRating' => ['safe', 'suggestive', 'erotica'],
            'excludedTags' => self::EXCLUDED_TAGS,
            'includes' => ['cover_art', 'author'],
            'limit' => 50,
        ]);
    }

    public function getMangaRatings(array $mangaIds): array
    {
        return $this->get('/statistics/manga', [
            'manga' => $mangaIds,
        ]);
    }

    public function getMangaFeed(string $mangaId, int $offset = 0, int $limit = 500, string $order = 'asc'): array
    {
        return $this->get('/manga/' . $mangaId . '/feed', [
            'limit' => $limit,
            // 'translatedLanguage' => ['en'],
            'includeUnavailable' => 1,
            'order' => ['chapter' => $order],
            'offset' => $offset,
        ]);
    }

    public function getMangaAggregate(string $mangaId): array
    {
        return $this->get('/manga/' . $mangaId . '/aggregate');
    }

    public function getManga(string $mangaId): array
    {
        return $this->get('/manga/' . $mangaId, [
            'includes' => ['cover_art', 'author']
        ]);
    }

    public function getMangaRecommendations(string $mangaId): array
    {
        // L'endpoint retourne des objets manga_recommendation avec des relationships
        // vers les manga (sans détails). Il faut d'abord récupérer les IDs, puis
        // fetcher les manga en batch avec leurs détails complets.
        $recommendations = $this->get('/manga/' . $mangaId . '/recommendation');

        $recommendedIds = [];
        foreach ($recommendations['data'] ?? [] as $item) {
            foreach ($item['relationships'] ?? [] as $rel) {
                if ($rel['type'] === 'manga' && $rel['id'] !== $mangaId) {
                    $recommendedIds[] = $rel['id'];
                }
            }
        }

        if (empty($recommendedIds)) {
            return ['data' => []];
        }

        return $this->get('/manga', [
            'ids'           => $recommendedIds,
            'includes'      => ['cover_art', 'author'],
            'contentRating' => ['safe', 'suggestive', 'erotica'],
            'excludedTags'  => self::EXCLUDED_TAGS,
            'limit'         => count($recommendedIds),
        ]);
    }

    private function get(string $endpoint, array $params = []): array
    {
        try {
            if (!$this->accessToken) {
                $this->authenticate();
            }

            $response = $this->client->request('GET', self::API_URL . $endpoint, [
                'query' => $params,
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->accessToken
                ]
            ]);

            // Handle 401 (Unauthorized) by refreshing the token and retrying
            if ($response->getStatusCode() === 401) {
                $this->refreshToken();
                $response = $this->client->request('GET', self::API_URL . $endpoint, [
                    'query' => $params,
                    'headers' => [
                        'Authorization' => 'Bearer ' . $this->accessToken
                    ]
                ]);
            }

            return $response->toArray();
        } catch (MangadexAuthenticationException $e) {
            throw $e;
        } catch (\Exception $e) {
            throw new MangadexApiException(
                sprintf('Failed to fetch data from Mangadex: %s', $e->getMessage()),
                $e
            );
        }
    }
}
