<?php

namespace MintyPHP\Tests\Module\Security\Service;

use MintyPHP\Module\Security\Service\SecurityCheckProcess;
use PHPUnit\Framework\TestCase;

class SecurityCheckProcessTest extends TestCase
{
    public function testStepsContainSixOrderedSteps(): void
    {
        $steps = (new SecurityCheckProcess())->steps();

        $this->assertCount(6, $steps);
        $this->assertSame('beauftragung', $steps[0]['key']);
        $this->assertSame(SecurityCheckProcess::STEP_PERFORM_CHECK, $steps[4]['key']);
        $this->assertSame('report', $steps[5]['key']);
    }

    public function testManualStepKeysExcludeTechChecklistStep(): void
    {
        $keys = (new SecurityCheckProcess())->manualStepKeys();

        $this->assertCount(5, $keys);
        $this->assertNotContains(SecurityCheckProcess::STEP_PERFORM_CHECK, $keys);
        $this->assertContains(SecurityCheckProcess::STEP_INFORMATION, $keys);
    }

    public function testInfoStepFieldsAreDefined(): void
    {
        $fields = (new SecurityCheckProcess())->stepFields(SecurityCheckProcess::STEP_INFORMATION);

        $this->assertNotEmpty($fields);
        $keys = array_column($fields, 'key');
        $this->assertContains('technical_contact', $keys);
        $this->assertContains('external_systems', $keys);
    }

    public function testReportStepRequiresOfficialCustomerReportField(): void
    {
        $process = new SecurityCheckProcess();

        $fields = $process->stepFields(SecurityCheckProcess::STEP_REPORT);
        $this->assertSame(
            [SecurityCheckProcess::FIELD_CUSTOMER_REPORT],
            array_column($fields, 'key')
        );

        // The owner must fill it before the final step can be completed.
        $this->assertContains(
            SecurityCheckProcess::FIELD_CUSTOMER_REPORT,
            $process->requiredFieldKeys(SecurityCheckProcess::STEP_REPORT)
        );
    }

    public function testRequiredFieldKeysExcludeOptionalFields(): void
    {
        $keys = (new SecurityCheckProcess())->requiredFieldKeys(SecurityCheckProcess::STEP_INFORMATION);

        $this->assertContains('technical_contact', $keys);
        $this->assertContains('deployment', $keys);
        $this->assertContains('system_info', $keys);
        $this->assertContains('external_systems', $keys);
        $this->assertNotContains('special_notes', $keys);
    }

    public function testSchedulingAndBlockerStepsRequireTwoDatetimeFields(): void
    {
        $process = new SecurityCheckProcess();
        $expected = [
            'scheduling' => ['golive_start', 'golive_end'],
            'blocker_appointment' => ['blocker_start', 'blocker_end'],
        ];

        foreach ($expected as $stepKey => $expectedKeys) {
            $fields = $process->stepFields($stepKey);
            $this->assertCount(2, $fields);
            foreach ($fields as $field) {
                $this->assertSame(SecurityCheckProcess::FIELD_DATETIME, $field['type']);
                $this->assertTrue($field['required']);
            }
            $this->assertSame($expectedKeys, $process->requiredFieldKeys($stepKey));
        }
    }

    public function testTechCheckItemsExtractsOnlyCheckItemsWithKeys(): void
    {
        $snapshot = ['items' => [
            ['type' => 'section', 'label' => 'Group'],
            ['type' => 'check', 'key' => 'tls', 'label' => 'TLS'],
            ['type' => 'check', 'key' => '', 'label' => 'No key'],
            ['type' => 'check', 'label' => 'Missing key'],
            'not-an-array',
        ]];

        $items = SecurityCheckProcess::techCheckItems($snapshot);

        $this->assertCount(1, $items);
        $this->assertSame('tls', $items[0]['key']);
    }

    public function testComputeProgressOpenWhenNothingDone(): void
    {
        $progress = (new SecurityCheckProcess())->computeProgress([], [], []);

        $this->assertSame(5, $progress['manual_total']);
        $this->assertSame(0, $progress['tech_total']);
        $this->assertSame(0, $progress['done']);
        $this->assertSame(0, $progress['percent']);
        $this->assertSame(SecurityCheckProcess::STATUS_OPEN, $progress['status']);
        $this->assertTrue($progress['step6_done']); // no tech items → trivially done
    }

    public function testComputeProgressInProgressWhenPartiallyDone(): void
    {
        $processState = ['beauftragung' => ['done' => true]];

        $progress = (new SecurityCheckProcess())->computeProgress($processState, [], []);

        $this->assertSame(1, $progress['done']);
        $this->assertSame(SecurityCheckProcess::STATUS_IN_PROGRESS, $progress['status']);
    }

    public function testComputeProgressCompletedWhenAllDone(): void
    {
        $process = new SecurityCheckProcess();
        $snapshot = ['items' => [
            ['type' => 'check', 'key' => 'a', 'label' => 'A'],
            ['type' => 'check', 'key' => 'b', 'label' => 'B'],
        ]];

        $processState = [];
        foreach ($process->manualStepKeys() as $key) {
            $processState[$key] = ['done' => true];
        }
        $techState = ['a' => ['done' => true], 'b' => ['done' => true]];

        $progress = $process->computeProgress($processState, $snapshot, $techState);

        $this->assertSame(7, $progress['total']); // 5 manual + 2 tech
        $this->assertSame(7, $progress['done']);
        $this->assertSame(100, $progress['percent']);
        $this->assertTrue($progress['step6_done']);
        $this->assertSame(SecurityCheckProcess::STATUS_COMPLETED, $progress['status']);
    }

    public function testComputeProgressStep6NotDoneWhenSomeTechItemsOpen(): void
    {
        $snapshot = ['items' => [
            ['type' => 'check', 'key' => 'a', 'label' => 'A'],
            ['type' => 'check', 'key' => 'b', 'label' => 'B'],
        ]];
        $techState = ['a' => ['done' => true]];

        $progress = (new SecurityCheckProcess())->computeProgress([], $snapshot, $techState);

        $this->assertFalse($progress['step6_done']);
        $this->assertSame(SecurityCheckProcess::STATUS_IN_PROGRESS, $progress['status']);
    }

    public function testComputeProgressArchivedOverridesDerivedStatus(): void
    {
        $progress = (new SecurityCheckProcess())->computeProgress([], [], [], true);

        $this->assertSame(SecurityCheckProcess::STATUS_ARCHIVED, $progress['status']);
    }

    public function testReportPrerequisitesMetWhenAllPrecedingStepsComplete(): void
    {
        $process = new SecurityCheckProcess();
        $snapshot = ['items' => [['type' => 'check', 'key' => 'a', 'label' => 'A']]];

        $processState = [];
        foreach ($process->manualStepKeys() as $key) {
            // The report step itself is intentionally left open — it does not gate itself.
            $processState[$key] = ['done' => $key !== SecurityCheckProcess::STEP_REPORT];
        }
        $techState = ['a' => ['done' => true]];

        $this->assertTrue($process->reportPrerequisitesMet($processState, $snapshot, $techState));
    }

    public function testReportPrerequisitesNotMetWhenAManualStepIsOpen(): void
    {
        $process = new SecurityCheckProcess();
        $snapshot = ['items' => [['type' => 'check', 'key' => 'a', 'label' => 'A']]];

        $processState = [];
        foreach ($process->manualStepKeys() as $key) {
            $processState[$key] = ['done' => true];
        }
        $processState['scheduling']['done'] = false; // one preceding step still open
        $techState = ['a' => ['done' => true]];

        $this->assertFalse($process->reportPrerequisitesMet($processState, $snapshot, $techState));
    }

    public function testReportPrerequisitesNotMetWhenTechChecklistIncomplete(): void
    {
        $process = new SecurityCheckProcess();
        $snapshot = ['items' => [
            ['type' => 'check', 'key' => 'a', 'label' => 'A'],
            ['type' => 'check', 'key' => 'b', 'label' => 'B'],
        ]];

        $processState = [];
        foreach ($process->manualStepKeys() as $key) {
            $processState[$key] = ['done' => true];
        }
        $techState = ['a' => ['done' => true]]; // 'b' still open

        $this->assertFalse($process->reportPrerequisitesMet($processState, $snapshot, $techState));
    }

    public function testReportPrerequisitesMetWithEmptyTechChecklist(): void
    {
        $process = new SecurityCheckProcess();

        $processState = [];
        foreach ($process->manualStepKeys() as $key) {
            $processState[$key] = ['done' => true];
        }

        // No tech items → checklist trivially complete; only the manual steps gate.
        $this->assertTrue($process->reportPrerequisitesMet($processState, [], []));
    }

    public function testStatusVariantMapping(): void
    {
        $this->assertSame('neutral', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_OPEN));
        $this->assertSame('warning', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_IN_PROGRESS));
        $this->assertSame('success', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_COMPLETED));
        $this->assertSame('neutral', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_ARCHIVED));
    }
}
