<?php

namespace MintyPHP\Tests\Module\Helpdesk\Service;

use MintyPHP\Module\Helpdesk\Repository\SoftwareProductRepository;
use MintyPHP\Module\Helpdesk\Service\SoftwareProductService;
use PHPUnit\Framework\TestCase;

class SoftwareProductServiceTest extends TestCase
{
    private function createService(?SoftwareProductRepository $repository = null): SoftwareProductService
    {
        $repository = $repository ?? $this->createMock(SoftwareProductRepository::class);

        return new SoftwareProductService($repository);
    }

    public function testFindByCodeReturnsNullForMissingProduct(): void
    {
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn(null);

        $service = $this->createService($repository);
        $this->assertNull($service->findByCode('NONEXISTENT'));
    }

    public function testFindByCodeReturnsProduct(): void
    {
        $product = ['id' => 1, 'code' => 'INFO', 'name' => 'Infopoints', 'active' => 1];
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn($product);

        $service = $this->createService($repository);
        $result = $service->findByCode('INFO');
        $this->assertSame('INFO', $result['code']);
    }

    public function testUpdateNameByCodeReturnsErrorForMissingProduct(): void
    {
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn(null);

        $service = $this->createService($repository);
        $result = $service->updateNameByCode('NONEXISTENT', 'Test');

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

    public function testUpdateNameByCodeTrimsWhitespace(): void
    {
        $product = ['id' => 1, 'code' => 'INFO', 'name' => '', 'active' => 1];
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn($product);
        $repository->expects($this->once())
            ->method('updateName')
            ->with('INFO', 'Trimmed Name')
            ->willReturn(true);

        $service = $this->createService($repository);
        $result = $service->updateNameByCode('INFO', '  Trimmed Name  ');

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

    public function testUpdateNameByCodeRejectsOverlongName(): void
    {
        $product = ['id' => 1, 'code' => 'INFO', 'name' => '', 'active' => 1];
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn($product);
        $repository->expects($this->never())->method('updateName');

        $service = $this->createService($repository);
        $result = $service->updateNameByCode('INFO', str_repeat('A', 256));

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

    public function testUpdateNameByCodeAllowsEmptyName(): void
    {
        $product = ['id' => 1, 'code' => 'INFO', 'name' => 'Old', 'active' => 1];
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn($product);
        $repository->expects($this->once())
            ->method('updateName')
            ->with('INFO', '')
            ->willReturn(true);

        $service = $this->createService($repository);
        $result = $service->updateNameByCode('INFO', '');

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

    // --- Handover schema tests ---

    private function createServiceWithProduct(array $product = []): array
    {
        $product = array_merge([
            'id' => 1,
            'code' => 'INFO',
            'name' => 'Infopoints',
            'active' => 1,
            'handover_protocol_schema' => null,
        ], $product);

        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn($product);

        return [$this->createService($repository), $repository];
    }

    public function testSaveHandoverSchemaWithValidFields(): void
    {
        [$service, $repository] = $this->createServiceWithProduct();
        $repository->expects($this->once())
            ->method('updateHandoverSchema')
            ->with('INFO', $this->callback(function ($json) {
                $data = json_decode($json, true);

                return $data['version'] === 1
                    && count($data['fields']) === 2
                    && $data['fields'][0]['key'] === 'handover_date'
                    && $data['fields'][1]['key'] === 'notes';
            }))
            ->willReturn(true);

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'date', 'label' => 'Handover date', 'required' => true],
            ['type' => 'textarea', 'label' => 'Notes', 'required' => false],
        ]);

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

    public function testSaveHandoverSchemaEmptyFieldsSetsNull(): void
    {
        [$service, $repository] = $this->createServiceWithProduct();
        $repository->expects($this->once())
            ->method('updateHandoverSchema')
            ->with('INFO', null)
            ->willReturn(true);

        $result = $service->saveHandoverSchema('INFO', []);

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

    public function testSaveHandoverSchemaRejectsDuplicateGeneratedKeys(): void
    {
        [$service] = $this->createServiceWithProduct();

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'text', 'label' => 'Name'],
            ['type' => 'text', 'label' => 'Name'],
        ]);

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

    public function testSaveHandoverSchemaRejectsUnknownType(): void
    {
        [$service] = $this->createServiceWithProduct();

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'unknown_type', 'label' => 'Field'],
        ]);

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

    public function testSaveHandoverSchemaRejectsMoreThan50Fields(): void
    {
        [$service] = $this->createServiceWithProduct();

        $fields = [];
        for ($i = 0; $i < 51; $i++) {
            $fields[] = ['type' => 'text', 'label' => 'Field ' . $i];
        }

        $result = $service->saveHandoverSchema('INFO', $fields);

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

    public function testSaveHandoverSchemaRejectsSelectWithNoOptions(): void
    {
        [$service] = $this->createServiceWithProduct();

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'select', 'label' => 'Choice', 'options' => []],
        ]);

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

    public function testSaveHandoverSchemaRejectsSelectOptionWithEmptyLabel(): void
    {
        [$service] = $this->createServiceWithProduct();

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'select', 'label' => 'Choice', 'options' => [
                ['label' => ''],
            ]],
        ]);

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

    public function testSaveHandoverSchemaAutoGeneratesOptionValues(): void
    {
        [$service, $repository] = $this->createServiceWithProduct();
        $repository->expects($this->once())
            ->method('updateHandoverSchema')
            ->with('INFO', $this->callback(function ($json) {
                $data = json_decode($json, true);
                $options = $data['fields'][0]['options'] ?? [];

                return count($options) === 2
                    && $options[0]['value'] === 'niedrig'
                    && $options[0]['label'] === 'Niedrig'
                    && $options[1]['value'] === 'sehr_hoch'
                    && $options[1]['label'] === 'Sehr hoch';
            }))
            ->willReturn(true);

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'select', 'label' => 'Risk', 'options' => [
                ['label' => 'Niedrig'],
                ['label' => 'Sehr hoch'],
            ]],
        ]);

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

    public function testSaveHandoverSchemaAllowsHeadingWithoutKey(): void
    {
        [$service, $repository] = $this->createServiceWithProduct();
        $repository->expects($this->once())
            ->method('updateHandoverSchema')
            ->willReturn(true);

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'heading', 'label' => 'Section Title'],
        ]);

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

    public function testSaveHandoverSchemaIncrementsVersion(): void
    {
        $existingSchema = json_encode(['version' => 3, 'fields' => []], JSON_THROW_ON_ERROR);
        [$service, $repository] = $this->createServiceWithProduct([
            'handover_protocol_schema' => $existingSchema,
        ]);

        $repository->expects($this->once())
            ->method('updateHandoverSchema')
            ->with('INFO', $this->callback(function ($json) {
                $data = json_decode($json, true);

                return $data['version'] === 4;
            }))
            ->willReturn(true);

        $result = $service->saveHandoverSchema('INFO', [
            ['type' => 'text', 'label' => 'Name'],
        ]);

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

    public function testSaveHandoverSchemaRejectsMissingProduct(): void
    {
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->method('findByCode')->willReturn(null);

        $service = $this->createService($repository);
        $result = $service->saveHandoverSchema('NONEXISTENT', [
            ['type' => 'text', 'label' => 'Name'],
        ]);

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

    public function testListPagedDelegatesToRepository(): void
    {
        $expected = ['total' => 2, 'rows' => [['code' => 'A'], ['code' => 'B']]];
        $repository = $this->createMock(SoftwareProductRepository::class);
        $repository->expects($this->once())
            ->method('listPaged')
            ->with(['search' => 'test'])
            ->willReturn($expected);

        $service = $this->createService($repository);
        $result = $service->listPaged(['search' => 'test']);

        $this->assertSame(2, $result['total']);
        $this->assertCount(2, $result['rows']);
    }
}
