<?php

namespace App\Domain\Scraping\Infrastructure\Service\Scraper;

use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface;
use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest;
use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingResult;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\HttpClient\HttpClient;

class AdvancedHtmlScraper implements ScraperInterface
{
    private const USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
        'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0'
    ];

    private const ACCEPT_HEADERS = [
        'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
        'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    ];

    private const ACCEPT_LANGUAGE_HEADERS = [
        'en-US,en;q=0.9',
        'en-US,en;q=0.8',
        'en-GB,en;q=0.9',
        'fr-FR,fr;q=0.9,en;q=0.8'
    ];

    private const RETRY_ATTEMPTS = 3;
    private const RETRY_DELAY = 2; // secondes
    private const REQUEST_TIMEOUT = 30;

    public function __construct(
        private readonly HttpClientInterface $httpClient
    ) {
        // Utiliser un client HTTP personnalisé si non fourni
        if (!$this->httpClient) {
            $this->httpClient = HttpClient::create([
                'timeout' => self::REQUEST_TIMEOUT,
                'verify_peer' => false,
                'verify_host' => false
            ]);
        }
    }

    public function scrape(ScrapingRequest $request): ScrapingResult
    {
        $scrapingParameters = $request->getScrapingParameters();

        try {
            $pages = !$scrapingParameters['nextPageSelector']
                ? $this->scrapeVerticalReader($request)
                : $this->scrapeHorizontalReader($request);

            return new ScrapingResult($pages, count($pages));
        } catch (\Exception $e) {
            throw new \RuntimeException('Advanced HTML scraping failed: ' . $e->getMessage(), 0, $e);
        }
    }

    public function supports(string $sourceType): bool
    {
        return 'advanced_html' === $sourceType;
    }

    private function scrapeVerticalReader(ScrapingRequest $request): array
    {
        $html = $this->fetchHtmlWithRetry($request->getChapterUrl());
        $crawler = new Crawler($html);
        $params = $request->getScrapingParameters();

        $images = $crawler->filter($params['imageSelector'])
            ->each(function ($node) {
                // Essayer plusieurs attributs pour trouver l'URL de l'image
                $src = $node->attr('src') ?:
                       $node->attr('data-src') ?:
                       $node->attr('data-lazy-src') ?:
                       $node->attr('data-original') ?:
                       $node->attr('data-zoom-image') ?:
                       $node->attr('data-full-src');

                return $this->cleanImageUrl($src);
            });

        return array_filter($images, fn ($url) => !empty($url));
    }

    private function scrapeHorizontalReader(ScrapingRequest $request): array
    {
        $pages = [];
        $currentUrl = $request->getChapterUrl();
        $params = $request->getScrapingParameters();
        $visitedUrls = new \SplObjectStorage();
        $maxPages = 200; // Limite de sécurité
        $pageCount = 0;

        while ($currentUrl && $pageCount < $maxPages) {
            // Éviter les boucles infinies
            if (isset($visitedUrls[$currentUrl])) {
                break;
            }
            $visitedUrls[$currentUrl] = true;

            $html = $this->fetchHtmlWithRetry($currentUrl);
            $crawler = new Crawler($html);

            // Récupérer l'image de la page
            $imageNode = $crawler->filter($params['imageSelector'])->first();
            if ($imageNode->count() > 0) {
                $imageUrl = $imageNode->attr('src') ?:
                           $imageNode->attr('data-src') ?:
                           $imageNode->attr('data-lazy-src') ?:
                           $imageNode->attr('data-original');

                if ($imageUrl) {
                    $imageUrl = $this->resolveRelativeUrl($imageUrl, $currentUrl);
                    $pages[] = $this->cleanImageUrl($imageUrl);
                }
            }

            // Chercher le lien suivant
            $nextLink = $crawler->filter($params['nextPageSelector'])->first();
            if ($nextLink->count() === 0) {
                break;
            }

            $nextUrl = $nextLink->attr('href');
            if (!$nextUrl) {
                break;
            }

            $currentUrl = $this->resolveRelativeUrl($nextUrl, $currentUrl);
            $pageCount++;

            // Pause entre les requêtes pour éviter la détection
            sleep(1);
        }

        return array_filter($pages, fn ($url) => !empty($url));
    }

    private function fetchHtmlWithRetry(string $url): string
    {
        $lastException = null;

        for ($attempt = 1; $attempt <= self::RETRY_ATTEMPTS; $attempt++) {
            try {
                return $this->fetchHtml($url);
            } catch (\Exception $e) {
                $lastException = $e;

                if ($attempt < self::RETRY_ATTEMPTS) {
                    // Attendre avant de réessayer
                    sleep(self::RETRY_DELAY * $attempt);
                }
            }
        }

        throw $lastException;
    }

    private function fetchHtml(string $url): string
    {
        $headers = $this->generateHeaders();

        try {
            $response = $this->httpClient->request('GET', $url, [
                'headers' => $headers,
                'timeout' => self::REQUEST_TIMEOUT
            ]);

            $statusCode = $response->getStatusCode();

            if ($statusCode >= 400) {
                throw new \RuntimeException("HTTP {$statusCode} error for URL: {$url}");
            }

            $content = $response->getContent();

            // Vérifier si on a été bloqué par Cloudflare
            if (strpos($content, 'cf-browser-verification') !== false ||
                strpos($content, 'Checking your browser') !== false) {
                throw new \RuntimeException('Blocked by Cloudflare protection');
            }

            return $content;
        } catch (\Exception $e) {
            throw new \RuntimeException('Failed to fetch HTML: ' . $e->getMessage(), 0, $e);
        }
    }

    private function generateHeaders(): array
    {
        return [
            'User-Agent' => self::USER_AGENTS[array_rand(self::USER_AGENTS)],
            'Accept' => self::ACCEPT_HEADERS[array_rand(self::ACCEPT_HEADERS)],
            'Accept-Language' => self::ACCEPT_LANGUAGE_HEADERS[array_rand(self::ACCEPT_LANGUAGE_HEADERS)],
            'Accept-Encoding' => 'gzip, deflate, br',
            'DNT' => '1',
            'Connection' => 'keep-alive',
            'Upgrade-Insecure-Requests' => '1',
            'Sec-Fetch-Dest' => 'document',
            'Sec-Fetch-Mode' => 'navigate',
            'Sec-Fetch-Site' => 'none',
            'Sec-Fetch-User' => '?1',
            'Cache-Control' => 'max-age=0'
        ];
    }

    private function resolveRelativeUrl(string $url, string $baseUrl): string
    {
        if (preg_match('/^https?:\/\//', $url)) {
            return $url;
        }

        $parsedBase = parse_url($baseUrl);
        $scheme = $parsedBase['scheme'];
        $host = $parsedBase['host'];
        $port = isset($parsedBase['port']) ? ':' . $parsedBase['port'] : '';

        if (strpos($url, '/') === 0) {
            // URL absolue relative à la racine
            return $scheme . '://' . $host . $port . $url;
        } else {
            // URL relative au chemin actuel
            $path = isset($parsedBase['path']) ? dirname($parsedBase['path']) : '';
            return $scheme . '://' . $host . $port . $path . '/' . $url;
        }
    }

    private function cleanImageUrl(string $url): string
    {
        if (empty($url)) {
            return '';
        }

        // Supprimer les caractères de contrôle
        $url = preg_replace('/[\x00-\x1F\x7F]/', '', trim($url));

        // Supprimer les paramètres de requête inutiles
        $url = preg_replace('/(\?|&)(utm_[^&]*|ref[^&]*|source[^&]*)/i', '', $url);

        return $url;
    }
}
