<?php

declare(strict_types=1);

use App\Domain\PreSave\PreSaveCampaign;
use App\Domain\PreSave\PreSaveCampaignRepositoryInterface;
use App\Domain\PreSave\PreSaveRegistration;
use App\Domain\PreSave\PreSaveRegistrationRepositoryInterface;
use App\Domain\PreSave\ValueObjects\CampaignStatus;
use App\Domain\PreSave\ValueObjects\RegistrationStatus;
use App\Domain\PreSave\ValueObjects\StreamingPlatform;
use App\Domain\PreSave\ValueObjects\TrackUrl;
use App\Framework\Core\ValueObjects\Timestamp;

// Mock Repository Implementations for Testing

class InMemoryPreSaveCampaignRepository implements PreSaveCampaignRepositoryInterface
{
    /** @var array<int, PreSaveCampaign> */
    private array $campaigns = [];
    private int $nextId = 1;

    public function save(PreSaveCampaign $campaign): PreSaveCampaign
    {
        // If campaign has no ID (new), use a wrapper to create with ID
        if ($campaign->id === null) {
            $campaign = $this->assignId($campaign);
        }

        $this->campaigns[$campaign->id] = $campaign;
        return $campaign;
    }

    private function assignId(PreSaveCampaign $campaign): PreSaveCampaign
    {
        $id = $this->nextId++;

        return new PreSaveCampaign(
            id: $id,
            title: $campaign->title,
            artistName: $campaign->artistName,
            coverImageUrl: $campaign->coverImageUrl,
            releaseDate: $campaign->releaseDate,
            trackUrls: $campaign->trackUrls,
            status: $campaign->status,
            createdAt: $campaign->createdAt,
            updatedAt: Timestamp::now(),
            description: $campaign->description,
            startDate: $campaign->startDate
        );
    }

    public function findById(int $id): ?PreSaveCampaign
    {
        return $this->campaigns[$id] ?? null;
    }

    /** @return array<PreSaveCampaign> */
    public function findAll(array $filters = []): array
    {
        return array_values($this->campaigns);
    }

    /** @return array<PreSaveCampaign> */
    public function findReadyForRelease(): array
    {
        $now = Timestamp::now();
        return array_filter(
            $this->campaigns,
            function (PreSaveCampaign $c) use ($now) {
                return $c->status->equals(CampaignStatus::ACTIVE)
                    && $c->releaseDate->isBefore($now);
            }
        );
    }

    /** @return array<PreSaveCampaign> */
    public function findReadyForProcessing(): array
    {
        return array_filter(
            $this->campaigns,
            fn(PreSaveCampaign $c) => $c->status->equals(CampaignStatus::RELEASED)
        );
    }

    public function delete(int $id): bool
    {
        if (isset($this->campaigns[$id])) {
            unset($this->campaigns[$id]);
            return true;
        }
        return false;
    }

    /** @return array<string, int> */
    public function getStatistics(int $campaignId): array
    {
        return [
            'total_registrations' => 0,
            'completed' => 0,
            'pending' => 0,
            'failed' => 0,
        ];
    }

    public function clear(): void
    {
        $this->campaigns = [];
        $this->nextId = 1;
    }
}

class InMemoryPreSaveRegistrationRepository implements PreSaveRegistrationRepositoryInterface
{
    /** @var array<int, PreSaveRegistration> */
    private array $registrations = [];
    private int $nextId = 1;

    public function save(PreSaveRegistration $registration): PreSaveRegistration
    {
        if ($registration->id === null) {
            $registration = $this->assignId($registration);
        }

        $this->registrations[$registration->id] = $registration;
        return $registration;
    }

    private function assignId(PreSaveRegistration $registration): PreSaveRegistration
    {
        $id = $this->nextId++;

        return new PreSaveRegistration(
            id: $id,
            campaignId: $registration->campaignId,
            userId: $registration->userId,
            platform: $registration->platform,
            status: $registration->status,
            registeredAt: $registration->registeredAt,
            processedAt: $registration->processedAt,
            errorMessage: $registration->errorMessage,
            retryCount: $registration->retryCount
        );
    }

    public function findForUserAndCampaign(
        string $userId,
        int $campaignId,
        StreamingPlatform $platform
    ): ?PreSaveRegistration {
        foreach ($this->registrations as $registration) {
            if ($registration->userId === $userId
                && $registration->campaignId === $campaignId
                && $registration->platform->equals($platform)
            ) {
                return $registration;
            }
        }
        return null;
    }

    /** @return array<PreSaveRegistration> */
    public function findByUserId(string $userId): array
    {
        return array_values(array_filter(
            $this->registrations,
            fn(PreSaveRegistration $r) => $r->userId === $userId
        ));
    }

    /** @return array<PreSaveRegistration> */
    public function findPendingByCampaign(int $campaignId): array
    {
        return array_values(array_filter(
            $this->registrations,
            function (PreSaveRegistration $r) use ($campaignId) {
                return $r->campaignId === $campaignId
                    && $r->status->equals(RegistrationStatus::PENDING);
            }
        ));
    }

    /** @return array<PreSaveRegistration> */
    public function findRetryable(int $campaignId, int $maxRetries): array
    {
        return array_values(array_filter(
            $this->registrations,
            function (PreSaveRegistration $r) use ($campaignId, $maxRetries) {
                return $r->campaignId === $campaignId
                    && $r->status->equals(RegistrationStatus::FAILED)
                    && $r->retryCount < $maxRetries;
            }
        ));
    }

    /** @return array<string, int> */
    public function getStatusCounts(int $campaignId): array
    {
        $registrations = array_filter(
            $this->registrations,
            fn(PreSaveRegistration $r) => $r->campaignId === $campaignId
        );

        $counts = [
            'pending' => 0,
            'completed' => 0,
            'failed' => 0,
        ];

        foreach ($registrations as $registration) {
            if ($registration->status->equals(RegistrationStatus::PENDING)) {
                $counts['pending']++;
            } elseif ($registration->status->equals(RegistrationStatus::COMPLETED)) {
                $counts['completed']++;
            } elseif ($registration->status->equals(RegistrationStatus::FAILED)) {
                $counts['failed']++;
            }
        }

        return $counts;
    }

    public function delete(int $id): bool
    {
        if (isset($this->registrations[$id])) {
            unset($this->registrations[$id]);
            return true;
        }
        return false;
    }

    public function clear(): void
    {
        $this->registrations = [];
        $this->nextId = 1;
    }
}

describe('PreSaveCampaignRepository', function () {
    beforeEach(function () {
        $this->repository = new InMemoryPreSaveCampaignRepository();
    });

    afterEach(function () {
        $this->repository->clear();
    });

    it('saves and finds campaigns by id', function () {
        $releaseDate = Timestamp::fromDateTime(new DateTimeImmutable('2025-01-15 00:00:00'));

        $campaign = PreSaveCampaign::create(
            title: 'Test Album',
            artistName: 'Test Artist',
            coverImageUrl: 'https://example.com/cover.jpg',
            releaseDate: $releaseDate,
            trackUrls: [TrackUrl::create(StreamingPlatform::SPOTIFY, 'spotify:track:123')]
        );

        $saved = $this->repository->save($campaign);

        expect($saved->id)->not->toBeNull();

        $found = $this->repository->findById($saved->id);

        expect($found)->not->toBeNull();
        expect($found->id)->toBe($saved->id);
        expect($found->title)->toBe('Test Album');
        expect($found->artistName)->toBe('Test Artist');
    });

    it('returns null when campaign not found', function () {
        $found = $this->repository->findById(999);
        expect($found)->toBeNull();
    });

    it('finds all campaigns', function () {
        $releaseDate1 = Timestamp::fromDateTime(new DateTimeImmutable('2025-01-15'));
        $releaseDate2 = Timestamp::fromDateTime(new DateTimeImmutable('2025-01-20'));

        $campaign1 = PreSaveCampaign::create(
            title: 'Album 1',
            artistName: 'Artist 1',
            coverImageUrl: 'https://example.com/cover1.jpg',
            releaseDate: $releaseDate1,
            trackUrls: [TrackUrl::create(StreamingPlatform::SPOTIFY, 'spotify:track:1')]
        );

        $campaign2 = PreSaveCampaign::create(
            title: 'Album 2',
            artistName: 'Artist 2',
            coverImageUrl: 'https://example.com/cover2.jpg',
            releaseDate: $releaseDate2,
            trackUrls: [TrackUrl::create(StreamingPlatform::SPOTIFY, 'spotify:track:2')]
        );

        $this->repository->save($campaign1);
        $this->repository->save($campaign2);

        $all = $this->repository->findAll();

        expect($all)->toHaveCount(2);
        expect($all[0]->title)->toBe('Album 1');
        expect($all[1]->title)->toBe('Album 2');
    });

    it('updates existing campaigns', function () {
        $releaseDate = Timestamp::fromDateTime(new DateTimeImmutable('2025-01-15'));

        $campaign = PreSaveCampaign::create(
            title: 'Original Title',
            artistName: 'Artist',
            coverImageUrl: 'https://example.com/cover.jpg',
            releaseDate: $releaseDate,
            trackUrls: [TrackUrl::create(StreamingPlatform::SPOTIFY, 'spotify:track:1')]
        );

        $saved = $this->repository->save($campaign);
        $updated = $saved->markAsReleased();
        $this->repository->save($updated);

        $found = $this->repository->findById($saved->id);
        expect($found->status->equals(CampaignStatus::RELEASED))->toBeTrue();
    });

    it('deletes campaigns', function () {
        $releaseDate = Timestamp::fromDateTime(new DateTimeImmutable('2025-01-15'));

        $campaign = PreSaveCampaign::create(
            title: 'Test Album',
            artistName: 'Test Artist',
            coverImageUrl: 'https://example.com/cover.jpg',
            releaseDate: $releaseDate,
            trackUrls: [TrackUrl::create(StreamingPlatform::SPOTIFY, 'spotify:track:123')]
        );

        $saved = $this->repository->save($campaign);
        expect($this->repository->findById($saved->id))->not->toBeNull();

        $deleted = $this->repository->delete($saved->id);
        expect($deleted)->toBeTrue();
        expect($this->repository->findById($saved->id))->toBeNull();
    });
});

describe('PreSaveRegistrationRepository', function () {
    beforeEach(function () {
        $this->repository = new InMemoryPreSaveRegistrationRepository();
    });

    afterEach(function () {
        $this->repository->clear();
    });

    it('saves and finds registrations', function () {
        $registration = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_456',
            platform: StreamingPlatform::SPOTIFY
        );

        $saved = $this->repository->save($registration);

        expect($saved->id)->not->toBeNull();
        expect($saved->campaignId)->toBe(1);
        expect($saved->userId)->toBe('user_456');
        expect($saved->platform->equals(StreamingPlatform::SPOTIFY))->toBeTrue();
    });

    it('finds registrations for user and campaign', function () {
        $reg1 = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_1',
            platform: StreamingPlatform::SPOTIFY
        );

        $reg2 = PreSaveRegistration::create(
            campaignId: 2,
            userId: 'user_1',
            platform: StreamingPlatform::SPOTIFY
        );

        $saved1 = $this->repository->save($reg1);
        $this->repository->save($reg2);

        $found = $this->repository->findForUserAndCampaign(
            'user_1',
            1,
            StreamingPlatform::SPOTIFY
        );

        expect($found)->not->toBeNull();
        expect($found->id)->toBe($saved1->id);
        expect($found->campaignId)->toBe(1);
    });

    it('finds registrations by user id', function () {
        $reg1 = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_123',
            platform: StreamingPlatform::SPOTIFY
        );

        $reg2 = PreSaveRegistration::create(
            campaignId: 2,
            userId: 'user_123',
            platform: StreamingPlatform::SPOTIFY
        );

        $reg3 = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_456',
            platform: StreamingPlatform::SPOTIFY
        );

        $this->repository->save($reg1);
        $this->repository->save($reg2);
        $this->repository->save($reg3);

        $userRegistrations = $this->repository->findByUserId('user_123');

        expect($userRegistrations)->toHaveCount(2);
        expect($userRegistrations[0]->campaignId)->toBe(1);
        expect($userRegistrations[1]->campaignId)->toBe(2);
    });

    it('finds pending registrations by campaign', function () {
        $pending = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_1',
            platform: StreamingPlatform::SPOTIFY
        );

        $completed = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_2',
            platform: StreamingPlatform::SPOTIFY
        );
        $savedCompleted = $this->repository->save($completed);
        $completed = $savedCompleted->markAsCompleted();
        $this->repository->save($completed);

        $savedPending = $this->repository->save($pending);

        $pendingRegistrations = $this->repository->findPendingByCampaign(1);

        expect($pendingRegistrations)->toHaveCount(1);
        expect($pendingRegistrations[0]->id)->toBe($savedPending->id);
        expect($pendingRegistrations[0]->status->equals(RegistrationStatus::PENDING))->toBeTrue();
    });

    it('finds retryable registrations', function () {
        $retryable = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_1',
            platform: StreamingPlatform::SPOTIFY
        );
        $savedRetryable = $this->repository->save($retryable);
        $retryable = $savedRetryable->markAsFailed('First failure');
        $this->repository->save($retryable);

        $maxedOut = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_2',
            platform: StreamingPlatform::SPOTIFY
        );
        $savedMaxedOut = $this->repository->save($maxedOut);
        $maxedOut = $savedMaxedOut->markAsFailed('Failure 1');
        $maxedOut = $this->repository->save($maxedOut);
        $maxedOut = $maxedOut->resetForRetry()->markAsFailed('Failure 2');
        $maxedOut = $this->repository->save($maxedOut);
        $maxedOut = $maxedOut->resetForRetry()->markAsFailed('Failure 3');
        $this->repository->save($maxedOut);

        $retryableList = $this->repository->findRetryable(1, 3);

        expect($retryableList)->toHaveCount(1);
        expect($retryableList[0]->id)->toBe($savedRetryable->id);
        expect($retryableList[0]->retryCount)->toBe(0);
    });

    it('deletes registrations', function () {
        $registration = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_456',
            platform: StreamingPlatform::SPOTIFY
        );

        $saved = $this->repository->save($registration);

        $deleted = $this->repository->delete($saved->id);
        expect($deleted)->toBeTrue();
    });

    it('updates existing registrations', function () {
        $registration = PreSaveRegistration::create(
            campaignId: 1,
            userId: 'user_456',
            platform: StreamingPlatform::SPOTIFY
        );

        $saved = $this->repository->save($registration);
        $updated = $saved->markAsCompleted();
        $this->repository->save($updated);

        $found = $this->repository->findForUserAndCampaign(
            'user_456',
            1,
            StreamingPlatform::SPOTIFY
        );
        expect($found->status->equals(RegistrationStatus::COMPLETED))->toBeTrue();
    });
});
