<?php

declare(strict_types=1);

use App\Domain\SmartLink\Enums\LinkStatus;
use App\Domain\SmartLink\Enums\LinkType;
use App\Domain\SmartLink\Enums\ServiceType;
use App\Domain\SmartLink\Exceptions\ShortCodeAlreadyExistsException;
use App\Domain\SmartLink\Exceptions\SmartLinkNotFoundException;
use App\Domain\SmartLink\Services\ShortCodeGenerator;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\DestinationUrl;
use App\Domain\SmartLink\ValueObjects\LinkTitle;
use App\Domain\SmartLink\ValueObjects\ShortCode;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\DateTime\SystemClock;
use Tests\Support\InMemoryLinkDestinationRepository;
use Tests\Support\InMemorySmartLinkRepository;

describe('SmartLinkService', function () {
    beforeEach(function () {
        $this->clock = new SystemClock();
        $this->linkRepository = new InMemorySmartLinkRepository();
        $this->destinationRepository = new InMemoryLinkDestinationRepository();
        $this->shortCodeGenerator = new ShortCodeGenerator($this->linkRepository);

        $this->service = new SmartLinkService(
            linkRepository: $this->linkRepository,
            destinationRepository: $this->destinationRepository,
            shortCodeGenerator: $this->shortCodeGenerator,
            clock: $this->clock
        );
    });

    describe('createLink', function () {
        it('creates link with auto-generated short code', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            expect($link->type)->toBe(LinkType::RELEASE);
            expect($link->title->toString())->toBe('My Album');
            expect($link->status)->toBe(LinkStatus::DRAFT);
            expect($link->userId)->toBeNull();
        });

        it('creates link with custom short code', function () {
            $customCode = ShortCode::fromString('custom');

            $link = $this->service->createLink(
                type: LinkType::BIO_LINK,
                title: LinkTitle::fromString('My Bio'),
                customShortCode: $customCode
            );

            expect($link->shortCode->equals($customCode))->toBeTrue();
        });

        it('creates link with user ID', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album'),
                userId: 'user123'
            );

            expect($link->userId)->toBe('user123');
            expect($link->isOwnedBy('user123'))->toBeTrue();
        });

        it('creates link with cover image', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album'),
                coverImageUrl: 'https://example.com/cover.jpg'
            );

            expect($link->coverImageUrl)->toBe('https://example.com/cover.jpg');
        });

        it('throws exception when custom short code already exists', function () {
            $shortCode = ShortCode::fromString('exist1');

            $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('First'),
                customShortCode: $shortCode
            );

            expect(fn() => $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Second'),
                customShortCode: $shortCode
            ))->toThrow(ShortCodeAlreadyExistsException::class);
        });

        it('persists link to repository', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $foundLink = $this->linkRepository->findById($link->id);
            expect($foundLink)->toBeInstanceOf(\App\Domain\SmartLink\Entities\SmartLink::class);
            expect($foundLink->id->equals($link->id))->toBeTrue();
        });
    });

    describe('findById', function () {
        it('finds existing link by ID', function () {
            $createdLink = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $foundLink = $this->service->findById($createdLink->id);

            expect($foundLink->id->equals($createdLink->id))->toBeTrue();
            expect($foundLink->title->toString())->toBe('My Album');
        });

        it('throws exception when link not found', function () {
            $nonExistentId = SmartLinkId::generate($this->clock);

            expect(fn() => $this->service->findById($nonExistentId))
                ->toThrow(SmartLinkNotFoundException::class);
        });
    });

    describe('findByShortCode', function () {
        it('finds existing link by short code', function () {
            $shortCode = ShortCode::fromString('abc123');

            $createdLink = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album'),
                customShortCode: $shortCode
            );

            $foundLink = $this->service->findByShortCode($shortCode);

            expect($foundLink->id->equals($createdLink->id))->toBeTrue();
            expect($foundLink->shortCode->equals($shortCode))->toBeTrue();
        });

        it('throws exception when short code not found', function () {
            $nonExistentCode = ShortCode::fromString('notfound');

            expect(fn() => $this->service->findByShortCode($nonExistentCode))
                ->toThrow(SmartLinkNotFoundException::class);
        });

        it('is case-insensitive for short code lookup', function () {
            $shortCode = ShortCode::fromString('AbCdEf');

            $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album'),
                customShortCode: $shortCode
            );

            $lowerCaseCode = ShortCode::fromString('abcdef');
            $foundLink = $this->service->findByShortCode($lowerCaseCode);

            expect($foundLink)->toBeInstanceOf(\App\Domain\SmartLink\Entities\SmartLink::class);
            expect($foundLink->shortCode->toString())->toBe('AbCdEf');
        });
    });

    describe('updateTitle', function () {
        it('updates link title', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Original Title')
            );

            $newTitle = LinkTitle::fromString('Updated Title');
            $updatedLink = $this->service->updateTitle($link->id, $newTitle);

            expect($updatedLink->title->toString())->toBe('Updated Title');
            expect($updatedLink->id->equals($link->id))->toBeTrue();
        });

        it('persists title update', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Original Title')
            );

            $newTitle = LinkTitle::fromString('Updated Title');
            $this->service->updateTitle($link->id, $newTitle);

            $foundLink = $this->service->findById($link->id);
            expect($foundLink->title->toString())->toBe('Updated Title');
        });
    });

    describe('publishLink', function () {
        it('publishes draft link', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            expect($link->status)->toBe(LinkStatus::DRAFT);

            $publishedLink = $this->service->publishLink($link->id);

            expect($publishedLink->status)->toBe(LinkStatus::ACTIVE);
            expect($publishedLink->isActive())->toBeTrue();
        });

        it('persists published status', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $this->service->publishLink($link->id);

            $foundLink = $this->service->findById($link->id);
            expect($foundLink->status)->toBe(LinkStatus::ACTIVE);
        });
    });

    describe('pauseLink', function () {
        it('pauses active link', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $publishedLink = $this->service->publishLink($link->id);
            expect($publishedLink->status)->toBe(LinkStatus::ACTIVE);

            $pausedLink = $this->service->pauseLink($link->id);
            expect($pausedLink->status)->toBe(LinkStatus::PAUSED);
        });
    });

    describe('addDestination', function () {
        it('adds destination to link', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $destination = $this->service->addDestination(
                linkId: $link->id,
                serviceType: ServiceType::SPOTIFY,
                url: DestinationUrl::fromString('https://open.spotify.com/album/123')
            );

            expect($destination->linkId->equals($link->id))->toBeTrue();
            expect($destination->serviceType)->toBe(ServiceType::SPOTIFY);
            expect($destination->url->toString())->toBe('https://open.spotify.com/album/123');
        });

        it('adds destination with priority', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $destination = $this->service->addDestination(
                linkId: $link->id,
                serviceType: ServiceType::SPOTIFY,
                url: DestinationUrl::fromString('https://open.spotify.com/album/123'),
                priority: 10
            );

            expect($destination->priority)->toBe(10);
        });

        it('adds default destination', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $destination = $this->service->addDestination(
                linkId: $link->id,
                serviceType: ServiceType::SPOTIFY,
                url: DestinationUrl::fromString('https://open.spotify.com/album/123'),
                isDefault: true
            );

            expect($destination->isDefault)->toBeTrue();
        });
    });

    describe('getDestinations', function () {
        it('retrieves all destinations for link', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $this->service->addDestination(
                linkId: $link->id,
                serviceType: ServiceType::SPOTIFY,
                url: DestinationUrl::fromString('https://spotify.com/album/1')
            );

            $this->service->addDestination(
                linkId: $link->id,
                serviceType: ServiceType::APPLE_MUSIC,
                url: DestinationUrl::fromString('https://music.apple.com/album/1')
            );

            $destinations = $this->service->getDestinations($link->id);

            expect($destinations)->toHaveCount(2);
        });

        it('returns empty array when no destinations', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $destinations = $this->service->getDestinations($link->id);

            expect($destinations)->toBeEmpty();
        });
    });

    describe('deleteLink', function () {
        it('deletes link from repository', function () {
            $link = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('My Album')
            );

            $this->service->deleteLink($link->id);

            expect(fn() => $this->service->findById($link->id))
                ->toThrow(SmartLinkNotFoundException::class);
        });
    });

    describe('getUserLinks', function () {
        it('retrieves all links for user', function () {
            $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Album 1'),
                userId: 'user123'
            );

            $this->service->createLink(
                type: LinkType::BIO_LINK,
                title: LinkTitle::fromString('Bio'),
                userId: 'user123'
            );

            $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Album 2'),
                userId: 'user456'
            );

            $userLinks = $this->service->getUserLinks('user123');

            expect($userLinks)->toHaveCount(2);
        });

        it('filters links by status', function () {
            $link1 = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Album 1'),
                userId: 'user123'
            );

            $link2 = $this->service->createLink(
                type: LinkType::RELEASE,
                title: LinkTitle::fromString('Album 2'),
                userId: 'user123'
            );

            $this->service->publishLink($link2->id);

            $draftLinks = $this->service->getUserLinks('user123', LinkStatus::DRAFT);
            $activeLinks = $this->service->getUserLinks('user123', LinkStatus::ACTIVE);

            expect($draftLinks)->toHaveCount(1);
            expect($activeLinks)->toHaveCount(1);
        });

        it('returns empty array when user has no links', function () {
            $userLinks = $this->service->getUserLinks('nonexistent');

            expect($userLinks)->toBeEmpty();
        });
    });
});
