<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\StructuredData\WebPage;
use App\Framework\Core\ValueObjects\StructuredData\WebPageType;
use App\Framework\Core\ValueObjects\StructuredData\Article;
use App\Framework\Core\ValueObjects\StructuredData\ArticleType;
use App\Framework\Core\ValueObjects\StructuredData\Organization;
use App\Framework\Core\ValueObjects\StructuredData\OrganizationType;
use App\Framework\Core\ValueObjects\StructuredData\Person;
use App\Framework\Core\ValueObjects\StructuredData\StructuredDataCollection;
use App\Framework\Core\ValueObjects\LinkCollection;
use App\Framework\Core\ValueObjects\HtmlLink;
use App\Framework\Http\Url\UrlFactory;

describe('WebPage Structured Data', function () {
    it('creates basic webpage schema', function () {
        $webpage = new WebPage(
            url: UrlFactory::parse('https://example.com/page'),
            name: 'Example Page'
        );

        $jsonLd = $webpage->toJsonLd();
        expect($jsonLd)->toHaveKey('@context', 'https://schema.org')
            ->and($jsonLd)->toHaveKey('@type', 'WebPage')
            ->and($jsonLd)->toHaveKey('url', 'https://example.com/page')
            ->and($jsonLd)->toHaveKey('name', 'Example Page');
    });

    it('includes optional webpage properties', function () {
        $webpage = new WebPage(
            url: UrlFactory::parse('https://example.com/page'),
            name: 'Example Page',
            type: WebPageType::WEB_PAGE,
            description: 'This is an example page',
            datePublished: new \DateTimeImmutable('2024-01-01'),
            dateModified: new \DateTimeImmutable('2024-01-15'),
            inLanguage: 'en',
            keywords: ['example', 'test', 'page']
        );

        $jsonLd = $webpage->toJsonLd();
        expect($jsonLd)->toHaveKey('description', 'This is an example page')
            ->and($jsonLd)->toHaveKey('datePublished')
            ->and($jsonLd)->toHaveKey('dateModified')
            ->and($jsonLd)->toHaveKey('inLanguage', 'en')
            ->and($jsonLd)->toHaveKey('keywords', 'example, test, page');
    });

    it('includes breadcrumb navigation', function () {
        $breadcrumb = new LinkCollection(
            HtmlLink::create('/', 'Home'),
            HtmlLink::create('/products', 'Products'),
            HtmlLink::create('/products/widgets', 'Widgets')
        );

        $webpage = new WebPage(
            url: UrlFactory::parse('https://example.com/products/widgets'),
            name: 'Widgets',
            breadcrumb: $breadcrumb
        );

        $jsonLd = $webpage->toJsonLd();
        expect($jsonLd)->toHaveKey('breadcrumb')
            ->and($jsonLd['breadcrumb'])->toHaveKey('@type', 'BreadcrumbList')
            ->and($jsonLd['breadcrumb'])->toHaveKey('itemListElement')
            ->and($jsonLd['breadcrumb']['itemListElement'])->toHaveCount(3)
            ->and($jsonLd['breadcrumb']['itemListElement'][0])->toHaveKey('position', 1)
            ->and($jsonLd['breadcrumb']['itemListElement'][0])->toHaveKey('name', 'Home')
            ->and($jsonLd['breadcrumb']['itemListElement'][2])->toHaveKey('position', 3);
    });

    it('renders as JSON-LD script tag', function () {
        $webpage = new WebPage(
            url: UrlFactory::parse('https://example.com/page'),
            name: 'Example Page'
        );

        $script = $webpage->toScript();
        expect($script)->toStartWith('<script type="application/ld+json">')
            ->and($script)->toEndWith('</script>')
            ->and($script)->toContain('"@context":"https://schema.org"')
            ->and($script)->toContain('"@type":"WebPage"');
    });

    it('supports immutable with methods', function () {
        $original = new WebPage(
            url: UrlFactory::parse('https://example.com/page'),
            name: 'Example Page'
        );

        $modified = $original->withDescription('New description')
            ->withKeywords('seo', 'test');

        expect($original->description)->toBeNull()
            ->and($original->keywords)->toBeEmpty()
            ->and($modified->description)->toBe('New description')
            ->and($modified->keywords)->toHaveCount(2);
    });
});

describe('Article Structured Data', function () {
    it('creates basic article schema', function () {
        $article = new Article(
            headline: 'Test Article',
            url: UrlFactory::parse('https://example.com/article'),
            datePublished: new \DateTimeImmutable('2024-01-01')
        );

        $jsonLd = $article->toJsonLd();
        expect($jsonLd)->toHaveKey('@context', 'https://schema.org')
            ->and($jsonLd)->toHaveKey('@type', 'Article')
            ->and($jsonLd)->toHaveKey('headline', 'Test Article')
            ->and($jsonLd)->toHaveKey('url', 'https://example.com/article')
            ->and($jsonLd)->toHaveKey('datePublished');
    });

    it('includes author and publisher', function () {
        $author = new Person(name: 'John Doe');
        $publisher = new Organization(name: 'Example Corp');

        $article = new Article(
            headline: 'Test Article',
            url: UrlFactory::parse('https://example.com/article'),
            datePublished: new \DateTimeImmutable('2024-01-01'),
            author: $author,
            publisher: $publisher
        );

        $jsonLd = $article->toJsonLd();
        expect($jsonLd)->toHaveKey('author')
            ->and($jsonLd['author'])->toHaveKey('@type', 'Person')
            ->and($jsonLd['author'])->toHaveKey('name', 'John Doe')
            ->and($jsonLd)->toHaveKey('publisher')
            ->and($jsonLd['publisher'])->toHaveKey('@type', 'Organization');
    });

    it('validates headline length', function () {
        $longHeadline = str_repeat('A', 111);

        expect(fn() => new Article(
            headline: $longHeadline,
            url: UrlFactory::parse('https://example.com/article'),
            datePublished: new \DateTimeImmutable()
        ))->toThrow(\InvalidArgumentException::class, 'headline should not exceed 110 characters');
    });

    it('supports immutable with methods', function () {
        $original = new Article(
            headline: 'Test',
            url: UrlFactory::parse('https://example.com/article'),
            datePublished: new \DateTimeImmutable()
        );

        $author = new Person(name: 'Jane Doe');
        $modified = $original->withAuthor($author);

        expect($original->author)->toBeNull()
            ->and($modified->author)->toBe($author);
    });
});

describe('Organization Structured Data', function () {
    it('creates basic organization schema', function () {
        $org = new Organization(name: 'Example Corp');

        $jsonLd = $org->toJsonLd();
        expect($jsonLd)->toHaveKey('@context', 'https://schema.org')
            ->and($jsonLd)->toHaveKey('@type', 'Organization')
            ->and($jsonLd)->toHaveKey('name', 'Example Corp');
    });

    it('includes optional organization properties', function () {
        $org = new Organization(
            name: 'Example Corp',
            type: OrganizationType::CORPORATION,
            url: UrlFactory::parse('https://example.com'),
            logo: UrlFactory::parse('https://example.com/logo.png'),
            description: 'A great company'
        );

        $jsonLd = $org->toJsonLd();
        expect($jsonLd)->toHaveKey('url', 'https://example.com/')
            ->and($jsonLd)->toHaveKey('logo', 'https://example.com/logo.png')
            ->and($jsonLd)->toHaveKey('description', 'A great company');
    });

    it('includes social media profiles', function () {
        $org = new Organization(
            name: 'Example Corp',
            sameAs: [
                'https://twitter.com/example',
                'https://facebook.com/example',
                'https://linkedin.com/company/example'
            ]
        );

        $jsonLd = $org->toJsonLd();
        expect($jsonLd)->toHaveKey('sameAs')
            ->and($jsonLd['sameAs'])->toHaveCount(3)
            ->and($jsonLd['sameAs'])->toContain('https://twitter.com/example');
    });

    it('supports adding social profiles immutably', function () {
        $original = new Organization(name: 'Example Corp');
        $modified = $original->addSocialProfile('https://twitter.com/example');

        expect($original->sameAs)->toBeEmpty()
            ->and($modified->sameAs)->toHaveCount(1);
    });
});

describe('Person Structured Data', function () {
    it('creates basic person schema', function () {
        $person = new Person(name: 'John Doe');

        $jsonLd = $person->toJsonLd();
        expect($jsonLd)->toHaveKey('@context', 'https://schema.org')
            ->and($jsonLd)->toHaveKey('@type', 'Person')
            ->and($jsonLd)->toHaveKey('name', 'John Doe');
    });

    it('includes optional person properties', function () {
        $person = new Person(
            name: 'John Doe',
            url: UrlFactory::parse('https://johndoe.com'),
            image: UrlFactory::parse('https://johndoe.com/photo.jpg'),
            jobTitle: 'Software Engineer',
            description: 'A skilled developer'
        );

        $jsonLd = $person->toJsonLd();
        expect($jsonLd)->toHaveKey('url', 'https://johndoe.com/')
            ->and($jsonLd)->toHaveKey('image', 'https://johndoe.com/photo.jpg')
            ->and($jsonLd)->toHaveKey('jobTitle', 'Software Engineer')
            ->and($jsonLd)->toHaveKey('description', 'A skilled developer');
    });

    it('includes employer organization', function () {
        $employer = new Organization(name: 'Example Corp');
        $person = new Person(
            name: 'John Doe',
            worksFor: $employer
        );

        $jsonLd = $person->toJsonLd();
        expect($jsonLd)->toHaveKey('worksFor')
            ->and($jsonLd['worksFor'])->toHaveKey('@type', 'Organization')
            ->and($jsonLd['worksFor'])->toHaveKey('name', 'Example Corp');
    });

    it('supports social profiles', function () {
        $person = new Person(
            name: 'John Doe',
            sameAs: [
                'https://twitter.com/johndoe',
                'https://github.com/johndoe'
            ]
        );

        $jsonLd = $person->toJsonLd();
        expect($jsonLd)->toHaveKey('sameAs')
            ->and($jsonLd['sameAs'])->toHaveCount(2);
    });
});

describe('StructuredDataCollection', function () {
    it('creates with variadic constructor', function () {
        $webpage = new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home');
        $org = new Organization(name: 'Example Corp');

        $collection = new StructuredDataCollection($webpage, $org);

        expect($collection->count())->toBe(2)
            ->and($collection->isEmpty())->toBeFalse();
    });

    it('implements Countable and IteratorAggregate', function () {
        $webpage = new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home');
        $collection = new StructuredDataCollection($webpage);

        expect(count($collection))->toBe(1);

        $items = [];
        foreach ($collection as $item) {
            $items[] = $item;
        }
        expect($items)->toHaveCount(1);
    });

    it('renders as separate JSON-LD script tags', function () {
        $webpage = new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home');
        $org = new Organization(name: 'Example Corp');

        $collection = new StructuredDataCollection($webpage, $org);
        $scripts = $collection->toScripts();

        expect($scripts)->toContain('<script type="application/ld+json">')
            ->and($scripts)->toContain('"@type":"WebPage"')
            ->and($scripts)->toContain('"@type":"Organization"')
            ->and(substr_count($scripts, '<script'))->toBe(2);
    });

    it('renders as single JSON-LD script with array', function () {
        $webpage = new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home');
        $org = new Organization(name: 'Example Corp');

        $collection = new StructuredDataCollection($webpage, $org);
        $script = $collection->toSingleScript();

        expect($script)->toContain('<script type="application/ld+json">')
            ->and($script)->toContain('"@type":"WebPage"')
            ->and($script)->toContain('"@type":"Organization"')
            ->and(substr_count($script, '<script'))->toBe(1);
    });

    it('returns empty string for empty collection', function () {
        $collection = new StructuredDataCollection();

        expect($collection->toScripts())->toBe('')
            ->and($collection->toSingleScript())->toBe('');
    });

    it('supports adding items immutably', function () {
        $webpage = new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home');
        $org = new Organization(name: 'Example Corp');

        $original = new StructuredDataCollection($webpage);
        $modified = $original->add($org);

        expect($original->count())->toBe(1)
            ->and($modified->count())->toBe(2);
    });

    it('supports merging collections immutably', function () {
        $collection1 = new StructuredDataCollection(
            new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home')
        );

        $collection2 = new StructuredDataCollection(
            new Organization(name: 'Example Corp')
        );

        $merged = $collection1->merge($collection2);

        expect($collection1->count())->toBe(1)
            ->and($collection2->count())->toBe(1)
            ->and($merged->count())->toBe(2);
    });

    it('converts to string as separate scripts', function () {
        $webpage = new WebPage(url: UrlFactory::parse('https://example.com'), name: 'Home');
        $collection = new StructuredDataCollection($webpage);

        $string = (string) $collection;

        expect($string)->toContain('<script type="application/ld+json">');
    });
});
