<?php

namespace MintyPHP\Tests\Module\Helpdesk\Service;

use MintyPHP\Module\Helpdesk\Repository\UpdateRepository;
use MintyPHP\Module\Helpdesk\Service\BcODataGateway;
use MintyPHP\Module\Helpdesk\Service\UpdateService;
use PHPUnit\Framework\TestCase;

class UpdateServiceTest extends TestCase
{
    private const TENANT_ID = 1;
    private const USER_ID = 42;

    private function createService(
        ?UpdateRepository $repository = null,
        ?BcODataGateway $bcGateway = null,
    ): UpdateService {
        $repository = $repository ?? $this->createMock(UpdateRepository::class);
        $bcGateway = $bcGateway ?? $this->createMock(BcODataGateway::class);

        return new UpdateService($repository, $bcGateway);
    }

    private function makeBcTicket(string $no = 'TK-001', string $category = 'UPDATE', string $customerNo = 'D10001', string $customerName = 'Acme Corp'): array
    {
        return [
            'No' => $no,
            'Customer_No' => $customerNo,
            'Cust_Name' => $customerName,
            'Category_1_Code' => $category,
            'Ticket_State' => 'Offen',
            'Created_On' => '2026-04-01T10:00:00Z',
            'Last_Activity_Date' => '2026-04-15T14:30:00Z',
            'Support_User_Name' => 'Max Mustermann',
        ];
    }

    // ── Assign tests ─────────────────────────────────────────────────

    public function testAssignUpdateHappyPath(): void
    {
        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findByTicketNo')->willReturn(null);
        $repo->expects($this->once())->method('insert')->willReturn(1);

        $service = $this->createService($repo);
        $result = $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            'D10001',
            'Acme Corp',
            'UPDATE',
            'DNS00001',
            'example.com',
            '2026-02-mysyde',
            'Initial update',
            self::USER_ID
        );

        $this->assertTrue($result['ok']);
        $this->assertSame(1, $result['id']);
        $this->assertEmpty($result['errors']);
    }

    public function testAssignUpdateUpdatesExistingRecord(): void
    {
        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findByTicketNo')->willReturn([
            'id' => 5,
            'ticket_no' => 'TK-001',
            'status' => 'open',
        ]);
        $repo->expects($this->once())->method('updateAssignment')->willReturn(true);
        $repo->expects($this->never())->method('insert');

        $service = $this->createService($repo);
        $result = $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            'D10001',
            'Acme Corp',
            'UPDATE',
            'DNS00002',
            'new.example.com',
            '2026-03-update',
            'Changed domain',
            self::USER_ID
        );

        $this->assertTrue($result['ok']);
        $this->assertSame(5, $result['id']);
    }

    public function testAssignUpdateRejectsEmptyTicketNo(): void
    {
        $service = $this->createService();
        $result = $service->assignUpdate(
            self::TENANT_ID,
            '',
            'D10001',
            'Acme Corp',
            'UPDATE',
            'DNS00001',
            'example.com',
            '',
            '',
            self::USER_ID
        );

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

    public function testAssignUpdateRejectsEmptyDebitorNo(): void
    {
        $service = $this->createService();
        $result = $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            '',
            'Acme Corp',
            'UPDATE',
            'DNS00001',
            'example.com',
            '',
            '',
            self::USER_ID
        );

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

    public function testAssignUpdateRejectsInvalidCategory(): void
    {
        $service = $this->createService();
        $result = $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            'D10001',
            'Acme Corp',
            'BUGFIX',
            'DNS00001',
            'example.com',
            '',
            '',
            self::USER_ID
        );

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

    public function testAssignUpdateSetsStatusAssignedWhenDomainProvided(): void
    {
        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findByTicketNo')->willReturn(null);
        $repo->expects($this->once())
            ->method('insert')
            ->with($this->callback(function (array $data): bool {
                return $data['status'] === 'assigned' && $data['domain_no'] === 'DNS00001';
            }))
            ->willReturn(1);

        $service = $this->createService($repo);
        $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            'D10001',
            'Acme Corp',
            'UPDATE',
            'DNS00001',
            'example.com',
            '',
            '',
            self::USER_ID
        );
    }

    public function testAssignUpdateSetsStatusOpenWhenNoDomainOrGitea(): void
    {
        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findByTicketNo')->willReturn(null);
        $repo->expects($this->once())
            ->method('insert')
            ->with($this->callback(function (array $data): bool {
                return $data['status'] === 'open' && $data['domain_no'] === '' && $data['gitea_path'] === '';
            }))
            ->willReturn(1);

        $service = $this->createService($repo);
        $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            'D10001',
            'Acme Corp',
            'UPDATE',
            '',
            '',
            '',
            '',
            self::USER_ID
        );
    }

    public function testAssignUpdateHandlesInsertFailure(): void
    {
        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findByTicketNo')->willReturn(null);
        $repo->method('insert')->willReturn(null);

        $service = $this->createService($repo);
        $result = $service->assignUpdate(
            self::TENANT_ID,
            'TK-001',
            'D10001',
            'Acme Corp',
            'UPDATE',
            'DNS00001',
            'example.com',
            '',
            '',
            self::USER_ID
        );

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

    // ── Merge list tests ─────────────────────────────────────────────

    public function testGetUpdatesListMergesBcWithLocalData(): void
    {
        $bcGateway = $this->createMock(BcODataGateway::class);
        $bcGateway->method('fetchUpdateTickets')->willReturn([
            'tickets' => [
                $this->makeBcTicket('TK-001', 'UPDATE'),
                $this->makeBcTicket('TK-002', 'UPDATE-HF'),
                $this->makeBcTicket('TK-003', 'UPDATE'),
            ],
            'truncated' => false,
        ]);

        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findAllByTenant')->willReturn([
            ['ticket_no' => 'TK-001', 'domain_no' => 'DNS00001', 'domain_url' => 'example.com', 'gitea_path' => '2026-02-mysyde', 'notes' => '', 'status' => 'assigned', 'id' => 1],
        ]);

        $service = $this->createService($repo, $bcGateway);
        $result = $service->getUpdatesList(self::TENANT_ID);

        $this->assertCount(3, $result['rows']);

        // TK-001 should be enriched with local data
        $tk001 = $result['rows'][0];
        $this->assertSame('TK-001', $tk001['ticket_no']);
        $this->assertSame('assigned', $tk001['status']);
        $this->assertSame('DNS00001', $tk001['domain_no']);
        $this->assertSame('2026-02-mysyde', $tk001['gitea_path']);

        // TK-002 should be open (no local record)
        $tk002 = $result['rows'][1];
        $this->assertSame('TK-002', $tk002['ticket_no']);
        $this->assertSame('open', $tk002['status']);
        $this->assertSame('', $tk002['domain_no']);
    }

    public function testGetUpdatesListFiltersByStatus(): void
    {
        $bcGateway = $this->createMock(BcODataGateway::class);
        $bcGateway->method('fetchUpdateTickets')->willReturn([
            'tickets' => [
                $this->makeBcTicket('TK-001'),
                $this->makeBcTicket('TK-002'),
            ],
            'truncated' => false,
        ]);

        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findAllByTenant')->willReturn([
            ['ticket_no' => 'TK-001', 'domain_no' => 'DNS00001', 'domain_url' => '', 'gitea_path' => '', 'notes' => '', 'status' => 'assigned', 'id' => 1],
        ]);

        $service = $this->createService($repo, $bcGateway);
        $result = $service->getUpdatesList(self::TENANT_ID, ['status' => 'open']);

        $this->assertCount(1, $result['rows']);
        $this->assertSame('TK-002', $result['rows'][0]['ticket_no']);
    }

    public function testGetUpdatesListFiltersByCategory(): void
    {
        $bcGateway = $this->createMock(BcODataGateway::class);
        $bcGateway->method('fetchUpdateTickets')->willReturn([
            'tickets' => [
                $this->makeBcTicket('TK-001', 'UPDATE'),
                $this->makeBcTicket('TK-002', 'UPDATE-HF'),
            ],
            'truncated' => false,
        ]);

        $repo = $this->createMock(UpdateRepository::class);
        $repo->method('findAllByTenant')->willReturn([]);

        $service = $this->createService($repo, $bcGateway);
        $result = $service->getUpdatesList(self::TENANT_ID, ['category' => 'UPDATE-HF']);

        $this->assertCount(1, $result['rows']);
        $this->assertSame('TK-002', $result['rows'][0]['ticket_no']);
    }

    // ── Static helper tests ──────────────────────────────────────────

    public function testStatusLabelsReturnExpectedValues(): void
    {
        $this->assertNotEmpty(UpdateService::statusLabel('open'));
        $this->assertNotEmpty(UpdateService::statusLabel('assigned'));
    }

    public function testCategoryLabelsReturnExpectedValues(): void
    {
        $this->assertNotEmpty(UpdateService::categoryLabel('UPDATE'));
        $this->assertNotEmpty(UpdateService::categoryLabel('UPDATE-HF'));
    }

    public function testStatusVariantsReturnValidValues(): void
    {
        $validVariants = ['neutral', 'info', 'warning', 'success', 'error'];
        $this->assertContains(UpdateService::statusVariant('open'), $validVariants);
        $this->assertContains(UpdateService::statusVariant('assigned'), $validVariants);
    }

    public function testTicketStateLabelsReturnExpectedValues(): void
    {
        $this->assertNotEmpty(UpdateService::ticketStateLabel('Offen'));
        $this->assertNotEmpty(UpdateService::ticketStateLabel('In Bearbeitung'));
        $this->assertNotEmpty(UpdateService::ticketStateLabel('Erledigt'));
        $this->assertNotEmpty(UpdateService::ticketStateLabel('Geschlossen'));
    }

    public function testTicketStateVariantsReturnValidValues(): void
    {
        $validVariants = ['neutral', 'info', 'warning', 'success', 'error'];
        $this->assertContains(UpdateService::ticketStateVariant('Offen'), $validVariants);
        $this->assertContains(UpdateService::ticketStateVariant('Erledigt'), $validVariants);
        $this->assertContains(UpdateService::ticketStateVariant('Geschlossen'), $validVariants);
    }
}
