<?php

namespace Tests\Unit\Services\Factories;

use App\Contracts\ArticleParserInterface;
use App\Services\Factories\ArticleParserFactory;
use App\Services\Parsers\BelgaArticleParser;
use App\Services\Parsers\VrtArticleParser;
use Exception;
use Tests\TestCase;

class ArticleParserFactoryTest extends TestCase
{
    public function test_get_parser_returns_vrt_parser_for_vrt_urls(): void
    {
        $vrtUrl = 'https://www.vrt.be/vrtnws/nl/2024/01/01/test-article/';

        $parser = ArticleParserFactory::getParser($vrtUrl);

        $this->assertInstanceOf(VrtArticleParser::class, $parser);
        $this->assertInstanceOf(ArticleParserInterface::class, $parser);
    }

    public function test_get_parser_returns_belga_parser_for_belga_urls(): void
    {
        $belgaUrl = 'https://www.belganewsagency.eu/nl/nieuws/binnenland/test-article';

        $parser = ArticleParserFactory::getParser($belgaUrl);

        $this->assertInstanceOf(BelgaArticleParser::class, $parser);
        $this->assertInstanceOf(ArticleParserInterface::class, $parser);
    }

    public function test_get_parser_throws_exception_for_unsupported_url(): void
    {
        $unsupportedUrl = 'https://www.example.com/article';

        $this->expectException(Exception::class);
        $this->expectExceptionMessage("No parser found for URL: {$unsupportedUrl}");

        ArticleParserFactory::getParser($unsupportedUrl);
    }

    public function test_get_supported_sources_returns_array_of_source_names(): void
    {
        $sources = ArticleParserFactory::getSupportedSources();

        $this->assertIsArray($sources);
        $this->assertCount(3, $sources);
        $this->assertContains('VRT News', $sources);
        $this->assertContains('Belga News Agency', $sources);
        $this->assertContains('The Guardian', $sources);
    }

    public function test_get_supported_sources_returns_sources_in_correct_order(): void
    {
        $sources = ArticleParserFactory::getSupportedSources();

        // Based on the factory's parser registration order
        $this->assertEquals('VRT News', $sources[0]);
        $this->assertEquals('Belga News Agency', $sources[1]);
    }

    public function test_register_parser_adds_new_parser_to_list(): void
    {
        // Create a mock parser class
        $mockParserClass = new class implements ArticleParserInterface
        {
            public function canParse(string $url): bool
            {
                return str_contains($url, 'test-parser.com');
            }

            public function extractData(string $html): array
            {
                return ['title' => 'Test Title'];
            }

            public function getSourceName(): string
            {
                return 'TestParser';
            }
        };

        $mockParserClassName = get_class($mockParserClass);

        // Register the mock parser
        ArticleParserFactory::registerParser($mockParserClassName);

        // Verify it's now included in supported sources
        $sources = ArticleParserFactory::getSupportedSources();
        $this->assertContains('TestParser', $sources);
        $this->assertCount(4, $sources); // Original 3 + 1 new

        // Verify it can be used to parse URLs
        $testUrl = 'https://test-parser.com/article';
        $parser = ArticleParserFactory::getParser($testUrl);
        $this->assertInstanceOf($mockParserClassName, $parser);
    }

    public function test_register_parser_prevents_duplicate_registration(): void
    {
        // Get initial source count
        $initialSources = ArticleParserFactory::getSupportedSources();
        $initialCount = count($initialSources);

        // Try to register an existing parser
        ArticleParserFactory::registerParser(VrtArticleParser::class);

        // Verify count hasn't changed
        $newSources = ArticleParserFactory::getSupportedSources();
        $this->assertCount($initialCount, $newSources);
        $this->assertEquals($initialSources, $newSources);
    }

    public function test_get_parser_uses_first_matching_parser(): void
    {
        // Create two mock parsers that can parse the same URL
        $mockParser1 = new class implements ArticleParserInterface
        {
            public function canParse(string $url): bool
            {
                return str_contains($url, 'shared-domain.com');
            }

            public function extractData(string $html): array
            {
                return ['parser' => 'first'];
            }

            public function getSourceName(): string
            {
                return 'FirstParser';
            }
        };

        $mockParser2 = new class implements ArticleParserInterface
        {
            public function canParse(string $url): bool
            {
                return str_contains($url, 'shared-domain.com');
            }

            public function extractData(string $html): array
            {
                return ['parser' => 'second'];
            }

            public function getSourceName(): string
            {
                return 'SecondParser';
            }
        };

        $mockParser1Class = get_class($mockParser1);
        $mockParser2Class = get_class($mockParser2);

        // Register both parsers
        ArticleParserFactory::registerParser($mockParser1Class);
        ArticleParserFactory::registerParser($mockParser2Class);

        // The first registered parser should be returned
        $testUrl = 'https://shared-domain.com/article';
        $parser = ArticleParserFactory::getParser($testUrl);

        // Should return the first parser since it was registered first
        $this->assertInstanceOf($mockParser1Class, $parser);
    }

    public function test_factory_maintains_parser_registration_across_calls(): void
    {
        // Create a mock parser
        $mockParser = new class implements ArticleParserInterface
        {
            public function canParse(string $url): bool
            {
                return str_contains($url, 'persistent-test.com');
            }

            public function extractData(string $html): array
            {
                return ['title' => 'Persistent Test'];
            }

            public function getSourceName(): string
            {
                return 'PersistentTestParser';
            }
        };

        $mockParserClass = get_class($mockParser);

        // Register the parser
        ArticleParserFactory::registerParser($mockParserClass);

        // Make multiple calls to verify persistence
        $parser1 = ArticleParserFactory::getParser('https://persistent-test.com/article1');
        $parser2 = ArticleParserFactory::getParser('https://persistent-test.com/article2');

        $this->assertInstanceOf($mockParserClass, $parser1);
        $this->assertInstanceOf($mockParserClass, $parser2);

        // Verify both instances are of the same class but different objects
        $this->assertEquals(get_class($parser1), get_class($parser2));
    }

    public function test_get_parser_creates_new_instance_each_time(): void
    {
        $vrtUrl = 'https://www.vrt.be/vrtnws/nl/test/';

        $parser1 = ArticleParserFactory::getParser($vrtUrl);
        $parser2 = ArticleParserFactory::getParser($vrtUrl);

        // Should be same class but different instances
        $this->assertEquals(get_class($parser1), get_class($parser2));
        $this->assertNotSame($parser1, $parser2);
    }

    public function test_get_supported_sources_creates_new_instances_for_each_call(): void
    {
        // This test ensures that getSupportedSources doesn't cause issues
        // by creating new instances each time it's called

        $sources1 = ArticleParserFactory::getSupportedSources();
        $sources2 = ArticleParserFactory::getSupportedSources();

        $this->assertEquals($sources1, $sources2);
        $this->assertCount(count($sources1), $sources2);
    }
}
