<?php

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

use App\Domain\Scraping\Domain\Contract\Service\ImageDownloaderInterface;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface;
use App\Domain\Scraping\Domain\Event\PageScrapingProgressed;
use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest;
use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingResult;
use App\Domain\Scraping\Domain\Model\ScrapingProgress;

class HtmlScraper implements ScraperInterface
{
    public function __construct(
        private readonly ImageDownloaderInterface $imageDownloader,
        private readonly MessageBusInterface $eventBus,
        private readonly HttpClientInterface $httpClient
    ) {
    }

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

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

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

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

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

        return $crawler->filter($params['imageSelector'])
            ->each(function ($node) {
                return $this->cleanImageUrl(
                    $node->attr('src') ?: $node->attr('data-src')
                );
            });
    }

    private function scrapeHorizontalReader(ScrapingRequest $request): array
    {
        $pages = [];
        $currentUrl = $request->getChapterUrl();
        $params = $request->getScrapingParameters();

        while ($currentUrl) {
            $html = $this->fetchHtml($currentUrl);
            $crawler = new Crawler($html);

            $imageUrl = $crawler->filter($params['imageSelector'])
                ->attr('src') ?: $crawler->filter($params['imageSelector'])
                ->attr('data-src');

            if (!preg_match('/^https?:\/\//', $imageUrl)) {
                $urlComponents = parse_url($params['chapterUrlFormat']);
                $scheme = $urlComponents['scheme'];
                $host = $urlComponents['host'];
                $imageUrl = $scheme.'://'.$host.'/'.ltrim($imageUrl, '/');
            }

            $pages[] = $this->cleanImageUrl($imageUrl);

            $nextLink = $crawler->filter($params['nextPageSelector']);
            $currentUrl = $nextLink->count() > 0 ? $nextLink->attr('href') : null;
        }

        return $pages;
    }

    private function fetchHtml(string $url): string
    {
        try {
            $response = $this->httpClient->request('GET', $url);
            $statusCode = $response->getStatusCode();

            if ($statusCode >= 300 && $statusCode < 400 || $statusCode === 404) {
                throw new \RuntimeException('Chapter Not Found at ' . $url);
            }

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

    private function cleanImageUrl(string $url): string
    {
        return preg_replace('/[\x00-\x1F\x7F]/', '', trim($url));
    }
}
