<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Database\EntityManager;
use App\Framework\LiveComponents\Attributes\TrackStateHistory;
use App\Framework\StateManagement\Database\DatabaseStateHistoryManager;
use App\Framework\StateManagement\Database\StateChangeType;
use App\Framework\StateManagement\Database\StateHistoryEntry;

// Test Component with TrackStateHistory
#[TrackStateHistory(
    trackIpAddress: true,
    trackUserAgent: true,
    trackChangedProperties: true
)]
final readonly class TestHistoryComponent
{
    public function __construct(
        public string $id
    ) {}
}

// Test Component without TrackStateHistory
final readonly class TestNoHistoryComponent
{
    public function __construct(
        public string $id
    ) {}
}

describe('DatabaseStateHistoryManager', function () {
    beforeEach(function () {
        // Mock EntityManager
        $this->entityManager = Mockery::mock(EntityManager::class);
        $this->unitOfWork = Mockery::mock(\App\Framework\Database\UnitOfWork::class);
        $this->entityManager->unitOfWork = $this->unitOfWork;

        // Create DatabaseStateHistoryManager
        $this->historyManager = new DatabaseStateHistoryManager(
            entityManager: $this->entityManager,
            logger: null
        );
    });

    afterEach(function () {
        Mockery::close();
    });

    describe('addHistoryEntry', function () {
        it('adds history entry to database', function () {
            $componentId = 'test-component:1';
            $stateData = json_encode(['count' => 42, 'message' => 'test']);
            $stateClass = 'TestComponentState';
            $version = 1;
            $changeType = StateChangeType::CREATED->value;
            $context = [
                'user_id' => 'user-123',
                'session_id' => 'session-456',
                'ip_address' => '127.0.0.1',
                'user_agent' => 'Mozilla/5.0'
            ];
            $changedProperties = ['count', 'message'];
            $currentChecksum = hash('sha256', $stateData);

            // Expect persist and commit
            $this->unitOfWork
                ->shouldReceive('persist')
                ->once()
                ->with(Mockery::type(StateHistoryEntry::class));

            $this->unitOfWork
                ->shouldReceive('commit')
                ->once();

            // Add history entry
            $this->historyManager->addHistoryEntry(
                componentId: $componentId,
                stateData: $stateData,
                stateClass: $stateClass,
                version: $version,
                changeType: $changeType,
                context: $context,
                changedProperties: $changedProperties,
                previousChecksum: null,
                currentChecksum: $currentChecksum
            );
        });
    });

    describe('getHistory', function () {
        it('returns history entries ordered by created_at DESC', function () {
            $componentId = 'test-component:2';

            // Mock: History entries
            $entries = [
                new StateHistoryEntry(
                    id: 2,
                    componentId: $componentId,
                    stateData: json_encode(['count' => 10]),
                    stateClass: 'TestState',
                    version: 2,
                    changeType: StateChangeType::UPDATED,
                    changedProperties: ['count'],
                    userId: 'user-123',
                    sessionId: 'session-456',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: 'checksum1',
                    currentChecksum: 'checksum2',
                    createdAt: Timestamp::now()
                ),
                new StateHistoryEntry(
                    id: 1,
                    componentId: $componentId,
                    stateData: json_encode(['count' => 0]),
                    stateClass: 'TestState',
                    version: 1,
                    changeType: StateChangeType::CREATED,
                    changedProperties: null,
                    userId: 'user-123',
                    sessionId: 'session-456',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: null,
                    currentChecksum: 'checksum1',
                    createdAt: Timestamp::now()
                ),
            ];

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(
                    StateHistoryEntry::class,
                    ['component_id' => $componentId],
                    ['created_at' => 'DESC'],
                    100,
                    0
                )
                ->andReturn($entries);

            // Get history
            $result = $this->historyManager->getHistory($componentId, limit: 100);

            expect($result)->toBeArray();
            expect($result)->toHaveCount(2);
            expect($result[0]->version)->toBe(2);
            expect($result[1]->version)->toBe(1);
        });
    });

    describe('getHistoryByVersion', function () {
        it('returns specific version from history', function () {
            $componentId = 'test-component:3';
            $version = 5;

            // Mock: History entry
            $entry = new StateHistoryEntry(
                id: 5,
                componentId: $componentId,
                stateData: json_encode(['count' => 50]),
                stateClass: 'TestState',
                version: $version,
                changeType: StateChangeType::UPDATED,
                changedProperties: ['count'],
                userId: 'user-123',
                sessionId: 'session-456',
                ipAddress: '127.0.0.1',
                userAgent: 'Mozilla/5.0',
                previousChecksum: 'checksum4',
                currentChecksum: 'checksum5',
                createdAt: Timestamp::now()
            );

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(
                    StateHistoryEntry::class,
                    [
                        'component_id' => $componentId,
                        'version' => $version
                    ],
                    Mockery::any(),
                    1
                )
                ->andReturn([$entry]);

            // Get specific version
            $result = $this->historyManager->getHistoryByVersion($componentId, $version);

            expect($result)->toBeInstanceOf(StateHistoryEntry::class);
            expect($result->version)->toBe(5);
            expect($result->componentId)->toBe($componentId);
        });

        it('returns null when version does not exist', function () {
            $componentId = 'test-component:4';
            $version = 999;

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->andReturn([]);

            // Get non-existent version
            $result = $this->historyManager->getHistoryByVersion($componentId, $version);

            expect($result)->toBeNull();
        });
    });

    describe('getHistoryByUser', function () {
        it('returns history entries for specific user', function () {
            $userId = 'user-123';

            // Mock: User's history entries
            $entries = [
                new StateHistoryEntry(
                    id: 1,
                    componentId: 'comp-1',
                    stateData: json_encode(['data' => 'test']),
                    stateClass: 'TestState',
                    version: 1,
                    changeType: StateChangeType::CREATED,
                    changedProperties: null,
                    userId: $userId,
                    sessionId: 'session-1',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: null,
                    currentChecksum: 'checksum1',
                    createdAt: Timestamp::now()
                ),
            ];

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(
                    StateHistoryEntry::class,
                    ['user_id' => $userId],
                    ['created_at' => 'DESC'],
                    100
                )
                ->andReturn($entries);

            // Get user history
            $result = $this->historyManager->getHistoryByUser($userId);

            expect($result)->toBeArray();
            expect($result)->toHaveCount(1);
            expect($result[0]->userId)->toBe($userId);
        });
    });

    describe('cleanup', function () {
        it('deletes old entries keeping only last N', function () {
            $componentId = 'test-component:5';
            $keepLast = 2;

            // Mock: 5 entries, we keep 2, delete 3
            $entries = array_map(
                fn(int $i) => new StateHistoryEntry(
                    id: $i,
                    componentId: $componentId,
                    stateData: json_encode(['version' => $i]),
                    stateClass: 'TestState',
                    version: $i,
                    changeType: StateChangeType::UPDATED,
                    changedProperties: null,
                    userId: 'user-123',
                    sessionId: 'session-456',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: "checksum{$i}",
                    currentChecksum: "checksum" . ($i + 1),
                    createdAt: Timestamp::now()
                ),
                range(5, 1, -1) // DESC order
            );

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(
                    StateHistoryEntry::class,
                    ['component_id' => $componentId],
                    ['created_at' => 'DESC']
                )
                ->andReturn($entries);

            // Expect remove for 3 oldest entries
            $this->unitOfWork
                ->shouldReceive('remove')
                ->times(3)
                ->with(Mockery::type(StateHistoryEntry::class));

            $this->unitOfWork
                ->shouldReceive('commit')
                ->once();

            // Cleanup
            $deletedCount = $this->historyManager->cleanup($componentId, $keepLast);

            expect($deletedCount)->toBe(3);
        });
    });

    describe('deleteHistory', function () {
        it('deletes all history for component', function () {
            $componentId = 'test-component:6';

            // Mock: 3 entries to delete
            $entries = array_map(
                fn(int $i) => new StateHistoryEntry(
                    id: $i,
                    componentId: $componentId,
                    stateData: json_encode(['data' => 'test']),
                    stateClass: 'TestState',
                    version: $i,
                    changeType: StateChangeType::UPDATED,
                    changedProperties: null,
                    userId: 'user-123',
                    sessionId: 'session-456',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: "checksum{$i}",
                    currentChecksum: "checksum" . ($i + 1),
                    createdAt: Timestamp::now()
                ),
                range(1, 3)
            );

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(
                    StateHistoryEntry::class,
                    ['component_id' => $componentId]
                )
                ->andReturn($entries);

            // Expect remove for all entries
            $this->unitOfWork
                ->shouldReceive('remove')
                ->times(3)
                ->with(Mockery::type(StateHistoryEntry::class));

            $this->unitOfWork
                ->shouldReceive('commit')
                ->once();

            // Delete history
            $deletedCount = $this->historyManager->deleteHistory($componentId);

            expect($deletedCount)->toBe(3);
        });
    });

    describe('isHistoryEnabled', function () {
        it('returns true when component has TrackStateHistory attribute', function () {
            $result = $this->historyManager->isHistoryEnabled(TestHistoryComponent::class);

            expect($result)->toBeTrue();
        });

        it('returns false when component does not have TrackStateHistory attribute', function () {
            $result = $this->historyManager->isHistoryEnabled(TestNoHistoryComponent::class);

            expect($result)->toBeFalse();
        });

        it('returns false when class does not exist', function () {
            $result = $this->historyManager->isHistoryEnabled('NonExistentClass');

            expect($result)->toBeFalse();
        });
    });

    describe('getStatistics', function () {
        it('returns statistics about history storage', function () {
            // Mock: Multiple entries
            $entries = [
                new StateHistoryEntry(
                    id: 1,
                    componentId: 'comp-1',
                    stateData: json_encode(['data' => 'test']),
                    stateClass: 'TestState',
                    version: 1,
                    changeType: StateChangeType::CREATED,
                    changedProperties: null,
                    userId: 'user-123',
                    sessionId: 'session-456',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: null,
                    currentChecksum: 'checksum1',
                    createdAt: Timestamp::fromString('2024-01-01 10:00:00')
                ),
                new StateHistoryEntry(
                    id: 2,
                    componentId: 'comp-2',
                    stateData: json_encode(['data' => 'test']),
                    stateClass: 'TestState',
                    version: 1,
                    changeType: StateChangeType::CREATED,
                    changedProperties: null,
                    userId: 'user-456',
                    sessionId: 'session-789',
                    ipAddress: '127.0.0.1',
                    userAgent: 'Mozilla/5.0',
                    previousChecksum: null,
                    currentChecksum: 'checksum2',
                    createdAt: Timestamp::fromString('2024-01-02 10:00:00')
                ),
            ];

            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(StateHistoryEntry::class, [])
                ->andReturn($entries);

            // Get statistics
            $stats = $this->historyManager->getStatistics();

            expect($stats)->toBeArray();
            expect($stats['total_entries'])->toBe(2);
            expect($stats['total_components'])->toBe(2);
            expect($stats['oldest_entry'])->toBeInstanceOf(Timestamp::class);
            expect($stats['newest_entry'])->toBeInstanceOf(Timestamp::class);
        });

        it('returns empty statistics when no entries exist', function () {
            $this->entityManager
                ->shouldReceive('findBy')
                ->once()
                ->with(StateHistoryEntry::class, [])
                ->andReturn([]);

            // Get statistics
            $stats = $this->historyManager->getStatistics();

            expect($stats)->toBeArray();
            expect($stats['total_entries'])->toBe(0);
            expect($stats['total_components'])->toBe(0);
            expect($stats['oldest_entry'])->toBeNull();
            expect($stats['newest_entry'])->toBeNull();
        });
    });
});
