<?php

namespace MintyPHP\Tests\Module\Helpdesk\Service;

use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
use MintyPHP\Module\Helpdesk\Repository\HandoverRevisionRepository;
use MintyPHP\Module\Helpdesk\Service\HandoverRevisionService;
use MintyPHP\Module\Helpdesk\Service\HandoverService;
use PHPUnit\Framework\TestCase;

class HandoverRevisionServiceTest extends TestCase
{
    private const TENANT_ID = 1;
    private const USER_ID = 42;
    private const HANDOVER_ID = 10;

    private function createService(
        ?HandoverRevisionRepository $revisionRepo = null,
        ?HandoverRepository $handoverRepo = null,
    ): HandoverRevisionService {
        $revisionRepo = $revisionRepo ?? $this->createMock(HandoverRevisionRepository::class);
        $handoverRepo = $handoverRepo ?? $this->createMock(HandoverRepository::class);

        return new HandoverRevisionService($revisionRepo, $handoverRepo);
    }

    // ── createRevision ───────────────────────────────────────────────

    public function testCreateRevisionIncrementsNumber(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('getLatestRevisionNumber')->willReturn(3);
        $revisionRepo->expects($this->once())
            ->method('insert')
            ->with($this->callback(function (array $data): bool {
                return $data['revision'] === 4;
            }))
            ->willReturn(100);

        $service = $this->createService($revisionRepo);
        $result = $service->createRevision(self::TENANT_ID, self::HANDOVER_ID, ['name' => 'Test'], 'draft', 1, self::USER_ID);

        $this->assertSame(100, $result);
    }

    public function testCreateRevisionStoresSnapshot(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('getLatestRevisionNumber')->willReturn(0);
        $revisionRepo->expects($this->once())
            ->method('insert')
            ->with($this->callback(function (array $data): bool {
                return $data['status'] === 'in_progress'
                    && $data['schema_version'] === 2
                    && $data['change_type'] === 'fields'
                    && json_decode($data['field_values'], true) === ['name' => 'Acme'];
            }))
            ->willReturn(1);

        $service = $this->createService($revisionRepo);
        $service->createRevision(self::TENANT_ID, self::HANDOVER_ID, ['name' => 'Acme'], 'in_progress', 2, self::USER_ID);
    }

    // ── computeDiff ──────────────────────────────────────────────────

    public function testComputeDiffDetectsAddedFields(): void
    {
        $service = $this->createService();
        $diff = $service->computeDiff([], ['name' => 'New']);

        $this->assertArrayHasKey('name', $diff);
        $this->assertSame('added', $diff['name']['type']);
        $this->assertNull($diff['name']['old']);
        $this->assertSame('New', $diff['name']['new']);
    }

    public function testComputeDiffDetectsRemovedFields(): void
    {
        $service = $this->createService();
        $diff = $service->computeDiff(['name' => 'Old'], []);

        $this->assertArrayHasKey('name', $diff);
        $this->assertSame('removed', $diff['name']['type']);
        $this->assertSame('Old', $diff['name']['old']);
        $this->assertNull($diff['name']['new']);
    }

    public function testComputeDiffDetectsChangedFields(): void
    {
        $service = $this->createService();
        $diff = $service->computeDiff(['name' => 'Old', 'priority' => 'low'], ['name' => 'New', 'priority' => 'low']);

        $this->assertArrayHasKey('name', $diff);
        $this->assertSame('changed', $diff['name']['type']);
        $this->assertSame('Old', $diff['name']['old']);
        $this->assertSame('New', $diff['name']['new']);
        $this->assertArrayNotHasKey('priority', $diff);
    }

    public function testComputeDiffHandlesEmptyArrays(): void
    {
        $service = $this->createService();
        $diff = $service->computeDiff([], []);

        $this->assertSame([], $diff);
    }

    // ── restoreRevision ──────────────────────────────────────────────

    public function testRestoreRevisionRequiresManagePermission(): void
    {
        $service = $this->createService();
        $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_CREATE);

        $this->assertFalse($result['ok']);
        $this->assertArrayHasKey('general', $result['errors']);
    }

    public function testRestoreRevisionRejectsInvalidRevision(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('findByRevision')->willReturn(null);

        $service = $this->createService($revisionRepo);
        $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 999, self::USER_ID, HandoverService::PERMISSION_MANAGE);

        $this->assertFalse($result['ok']);
    }

    public function testRestoreRevisionCreatesNewRevision(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('findByRevision')->willReturn([
            'id' => 1,
            'revision' => 2,
            'field_values' => '{"name":"Old value"}',
            'status' => 'draft',
        ]);
        $revisionRepo->method('getLatestRevisionNumber')->willReturn(5);
        $revisionRepo->expects($this->once())
            ->method('insert')
            ->with($this->callback(function (array $data): bool {
                return $data['revision'] === 6
                    && $data['change_type'] === 'restore'
                    && json_decode($data['field_values'], true) === ['name' => 'Old value'];
            }))
            ->willReturn(200);

        $handoverRepo = $this->createMock(HandoverRepository::class);
        $handoverRepo->method('findById')->willReturn([
            'id' => self::HANDOVER_ID,
            'status' => 'in_progress',
            'field_values' => '{"name":"Current value"}',
            'schema_snapshot' => '{"version":1,"fields":[]}',
        ]);
        $handoverRepo->expects($this->once())->method('beginTransaction');
        $handoverRepo->expects($this->once())->method('commitTransaction');
        $handoverRepo->expects($this->never())->method('rollbackTransaction');
        $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true);
        $handoverRepo->expects($this->once())->method('updateStatus')->willReturn(true);

        $service = $this->createService($revisionRepo, $handoverRepo);
        $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 2, self::USER_ID, HandoverService::PERMISSION_MANAGE);

        $this->assertTrue($result['ok']);
    }

    public function testRestoreRevisionSkipsStatusUpdateWhenSame(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('findByRevision')->willReturn([
            'id' => 1,
            'revision' => 1,
            'field_values' => '{"name":"Old"}',
            'status' => 'draft',
        ]);
        $revisionRepo->method('getLatestRevisionNumber')->willReturn(3);
        $revisionRepo->method('insert')->willReturn(100);

        $handoverRepo = $this->createMock(HandoverRepository::class);
        $handoverRepo->method('findById')->willReturn([
            'id' => self::HANDOVER_ID,
            'status' => 'draft',
            'field_values' => '{"name":"Current"}',
            'schema_snapshot' => '{"version":1,"fields":[]}',
        ]);
        $handoverRepo->expects($this->once())->method('beginTransaction');
        $handoverRepo->expects($this->once())->method('commitTransaction');
        $handoverRepo->expects($this->never())->method('rollbackTransaction');
        $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true);
        $handoverRepo->expects($this->never())->method('updateStatus');

        $service = $this->createService($revisionRepo, $handoverRepo);
        $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE);

        $this->assertTrue($result['ok']);
    }

    public function testRestoreRevisionRollsBackWhenFieldUpdateFails(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('findByRevision')->willReturn([
            'id' => 1,
            'revision' => 1,
            'field_values' => '{"name":"Old"}',
            'status' => 'draft',
        ]);
        $revisionRepo->expects($this->never())->method('insert');

        $handoverRepo = $this->createMock(HandoverRepository::class);
        $handoverRepo->method('findById')->willReturn([
            'id' => self::HANDOVER_ID,
            'status' => 'draft',
            'field_values' => '{"name":"Current"}',
            'schema_snapshot' => '{"version":1,"fields":[]}',
        ]);
        $handoverRepo->expects($this->once())->method('beginTransaction');
        $handoverRepo->expects($this->once())->method('rollbackTransaction');
        $handoverRepo->expects($this->never())->method('commitTransaction');
        $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(false);
        $handoverRepo->expects($this->never())->method('updateStatus');

        $service = $this->createService($revisionRepo, $handoverRepo);
        $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE);

        $this->assertFalse($result['ok']);
        $this->assertArrayHasKey('general', $result['errors']);
    }

    public function testRestoreRevisionRollsBackWhenRevisionInsertFails(): void
    {
        $revisionRepo = $this->createMock(HandoverRevisionRepository::class);
        $revisionRepo->method('findByRevision')->willReturn([
            'id' => 1,
            'revision' => 1,
            'field_values' => '{"name":"Old"}',
            'status' => 'draft',
        ]);
        $revisionRepo->method('getLatestRevisionNumber')->willReturn(3);
        $revisionRepo->expects($this->once())->method('insert')->willReturn(null);

        $handoverRepo = $this->createMock(HandoverRepository::class);
        $handoverRepo->method('findById')->willReturn([
            'id' => self::HANDOVER_ID,
            'status' => 'draft',
            'field_values' => '{"name":"Current"}',
            'schema_snapshot' => '{"version":1,"fields":[]}',
        ]);
        $handoverRepo->expects($this->once())->method('beginTransaction');
        $handoverRepo->expects($this->once())->method('rollbackTransaction');
        $handoverRepo->expects($this->never())->method('commitTransaction');
        $handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true);
        $handoverRepo->expects($this->never())->method('updateStatus');

        $service = $this->createService($revisionRepo, $handoverRepo);
        $result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE);

        $this->assertFalse($result['ok']);
    }
}
