<?php

namespace MintyPHP\Tests\Module\Security\Service;

use MintyPHP\Module\Security\Repository\SecurityCheckTemplateRepository;
use MintyPHP\Module\Security\Service\SecurityCheckTemplateService;
use PHPUnit\Framework\TestCase;

class SecurityCheckTemplateServiceTest extends TestCase
{
    private const TENANT_ID = 1;

    public function testCreateRejectsEmptyCode(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->expects($this->never())->method('insert');
        $service = new SecurityCheckTemplateService($repo);

        $result = $service->create(self::TENANT_ID, '', 'Name', 1);

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

    public function testCreateRejectsInvalidCode(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $service = new SecurityCheckTemplateService($repo);

        $result = $service->create(self::TENANT_ID, 'has spaces!', 'Name', 1);

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

    public function testCreateRejectsDuplicateCode(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('codeExists')->willReturn(true);
        $service = new SecurityCheckTemplateService($repo);

        $result = $service->create(self::TENANT_ID, 'intranet', 'Intranet', 1);

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

    public function testCreateSucceeds(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('codeExists')->willReturn(false);
        $repo->method('insert')->willReturn(12);
        $service = new SecurityCheckTemplateService($repo);

        $result = $service->create(self::TENANT_ID, 'intranet', 'Intranet', 1);

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

    public function testSaveTechSchemaRejectsMissingTemplate(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('findById')->willReturn(null);
        $repo->expects($this->never())->method('updateSchema');
        $service = new SecurityCheckTemplateService($repo);

        $result = $service->saveTechSchema(self::TENANT_ID, 99, [['type' => 'check', 'label' => 'X']], 1);

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

    public function testSaveTechSchemaSlugifiesKeysBumpsVersionAndKeepsSections(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('findById')->willReturn(['id' => 1, 'version' => 3]);

        $capturedJson = null;
        $capturedVersion = null;
        $repo->method('updateSchema')->willReturnCallback(function (int $t, int $id, string $json, int $version) use (&$capturedJson, &$capturedVersion) {
            $capturedJson = $json;
            $capturedVersion = $version;

            return true;
        });

        $service = new SecurityCheckTemplateService($repo);
        $result = $service->saveTechSchema(self::TENANT_ID, 1, [
            ['type' => 'section', 'label' => 'Intranet'],
            ['type' => 'check', 'label' => 'TLS überall erzwungen', 'hint' => 'check certs'],
        ], 1);

        $this->assertTrue($result['ok']);
        $this->assertSame(4, $capturedVersion);

        $data = json_decode((string) $capturedJson, true);
        $this->assertSame(4, $data['version']);
        $this->assertSame('section', $data['items'][0]['type']);
        $this->assertSame('check', $data['items'][1]['type']);
        $this->assertSame('tls_ueberall_erzwungen', $data['items'][1]['key']);
        $this->assertSame('check certs', $data['items'][1]['hint']);
    }

    public function testSaveTechSchemaPersistsMarkdownAndOmitsEmpty(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]);

        $capturedJson = null;
        $repo->method('updateSchema')->willReturnCallback(function (int $t, int $id, string $json) use (&$capturedJson) {
            $capturedJson = $json;

            return true;
        });

        $service = new SecurityCheckTemplateService($repo);
        $service->saveTechSchema(self::TENANT_ID, 1, [
            ['type' => 'check', 'label' => 'TLS', 'markdown' => '**Verify** the certificate chain.'],
            ['type' => 'check', 'label' => 'Backup', 'markdown' => '   '],
        ], 1);

        $data = json_decode((string) $capturedJson, true);
        $this->assertSame('**Verify** the certificate chain.', $data['items'][0]['markdown']);
        $this->assertArrayNotHasKey('markdown', $data['items'][1]);
    }

    public function testSaveTechSchemaDeduplicatesKeys(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]);

        $capturedJson = null;
        $repo->method('updateSchema')->willReturnCallback(function (int $t, int $id, string $json) use (&$capturedJson) {
            $capturedJson = $json;

            return true;
        });

        $service = new SecurityCheckTemplateService($repo);
        $service->saveTechSchema(self::TENANT_ID, 1, [
            ['type' => 'check', 'label' => 'Backup'],
            ['type' => 'check', 'label' => 'Backup'],
        ], 1);

        $data = json_decode((string) $capturedJson, true);
        $this->assertSame('backup', $data['items'][0]['key']);
        $this->assertSame('backup_2', $data['items'][1]['key']);
    }

    public function testSaveTechSchemaRejectsItemWithoutLabel(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]);
        $repo->expects($this->never())->method('updateSchema');

        $service = new SecurityCheckTemplateService($repo);
        $result = $service->saveTechSchema(self::TENANT_ID, 1, [['type' => 'check', 'label' => '']], 1);

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

    public function testSaveTechSchemaRejectsTooManyItems(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]);

        $items = [];
        for ($i = 0; $i <= SecurityCheckTemplateService::MAX_ITEMS; $i++) {
            $items[] = ['type' => 'check', 'label' => 'Item ' . $i];
        }

        $service = new SecurityCheckTemplateService($repo);
        $result = $service->saveTechSchema(self::TENANT_ID, 1, $items, 1);

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

    public function testUpdateMetaRejectsEmptyName(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $repo->expects($this->never())->method('updateMeta');
        $service = new SecurityCheckTemplateService($repo);

        $result = $service->updateMeta(self::TENANT_ID, 1, '', true, 1);

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

    public function testDecodeSchemaReturnsItems(): void
    {
        $repo = $this->createMock(SecurityCheckTemplateRepository::class);
        $service = new SecurityCheckTemplateService($repo);

        $decoded = $service->decodeSchema([
            'version' => 5,
            'tech_schema_json' => json_encode(['version' => 5, 'items' => [['type' => 'check', 'key' => 'a', 'label' => 'A']]]),
        ]);

        $this->assertSame(5, $decoded['version']);
        $this->assertCount(1, $decoded['items']);
    }
}
