<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\TestCase;

final class AgentSystemEnforcementContractTest extends TestCase
{
    use AgentSystemContractSupport;
    use ProjectFileAssertionSupport;

    public function testEnforcementPolicyReferencesOnlyKnownQualityGates(): void
    {
        $gates = $this->decodeAgentJson('.agents/checks/quality-gates.json');
        $policy = $this->decodeAgentJson('.agents/checks/enforcement-policy.json');

        $knownGateIds = array_map(
            static fn (array $gate): string => (string) ($gate['id'] ?? ''),
            is_array($gates['gates'] ?? null) ? $gates['gates'] : []
        );

        $required = is_array($policy['gate_policy']['required_gates_always'] ?? null)
            ? $policy['gate_policy']['required_gates_always']
            : [];
        $manual = is_array($policy['gate_policy']['manual_non_blocking'] ?? null)
            ? $policy['gate_policy']['manual_non_blocking']
            : [];
        $periodic = is_array($policy['gate_policy']['periodic_non_blocking'] ?? null)
            ? $policy['gate_policy']['periodic_non_blocking']
            : [];

        foreach (array_merge($required, $manual, $periodic) as $gateId) {
            $this->assertContains($gateId, $knownGateIds, 'Unknown gate id in enforcement-policy.json: ' . (string) $gateId);
        }
    }

    public function testGuardEnforcementMapCoversAllGuardsExactlyOnce(): void
    {
        $catalog = $this->decodeAgentJson('.agents/checks/guard-catalog.json');
        $map = $this->decodeAgentJson('.agents/checks/guard-enforcement-map.json');

        $knownGuardIds = array_map(
            static fn (array $guard): string => (string) ($guard['id'] ?? ''),
            is_array($catalog['guards'] ?? null) ? $catalog['guards'] : []
        );
        $entries = is_array($map['entries'] ?? null) ? $map['entries'] : [];
        $mappedGuardIds = array_map(
            static fn (array $entry): string => (string) ($entry['guard_id'] ?? ''),
            $entries
        );

        $this->assertSame($mappedGuardIds, array_values(array_unique($mappedGuardIds)), 'Duplicate guard_id in guard-enforcement-map.json.');

        foreach ($knownGuardIds as $guardId) {
            $this->assertContains($guardId, $mappedGuardIds, 'Missing guard mapping: ' . $guardId);
        }
        foreach ($mappedGuardIds as $guardId) {
            $this->assertContains($guardId, $knownGuardIds, 'Unknown guard_id in guard-enforcement-map.json: ' . $guardId);
        }

        foreach ($entries as $entry) {
            $mode = (string) ($entry['enforcement_mode'] ?? '');
            $this->assertContains(
                $mode,
                ['automated', 'hybrid', 'reviewer'],
                'Invalid enforcement_mode in guard-enforcement-map.json for ' . (string) ($entry['guard_id'] ?? '')
            );
        }
    }

    public function testAutomatedGuardMappingsReferenceExistingTestFiles(): void
    {
        $map = $this->decodeAgentJson('.agents/checks/guard-enforcement-map.json');
        $entries = is_array($map['entries'] ?? null) ? $map['entries'] : [];
        $root = $this->projectRootPath();

        foreach ($entries as $entry) {
            $guardId = (string) ($entry['guard_id'] ?? '');
            $mode = (string) ($entry['enforcement_mode'] ?? '');
            if ($mode !== 'automated') {
                continue;
            }

            $sources = is_array($entry['evidence_source'] ?? null) ? $entry['evidence_source'] : [];
            $testPaths = array_values(array_filter(
                $sources,
                static fn ($source): bool => is_string($source) && str_starts_with($source, 'tests/')
            ));

            $this->assertNotSame([], $testPaths, "Automated guard {$guardId} must reference at least one test file in evidence_source.");

            foreach ($testPaths as $testPath) {
                $this->assertFileExists($root . '/' . $testPath, "Automated guard {$guardId} references missing test file: {$testPath}");
            }
        }
    }

    public function testGuardAndGateReferencesAcrossRepoUseKnownIds(): void
    {
        $catalog = $this->decodeAgentJson('.agents/checks/guard-catalog.json');
        $gates = $this->decodeAgentJson('.agents/checks/quality-gates.json');

        $knownGuardIds = array_fill_keys(array_map(
            static fn (array $guard): string => (string) ($guard['id'] ?? ''),
            is_array($catalog['guards'] ?? null) ? $catalog['guards'] : []
        ), true);
        $knownGateIds = array_fill_keys(array_map(
            static fn (array $gate): string => (string) ($gate['id'] ?? ''),
            is_array($gates['gates'] ?? null) ? $gates['gates'] : []
        ), true);

        $unknown = [];
        foreach ($this->idReferenceScanFiles() as $relativePath) {
            $content = $this->readProjectFile($relativePath);

            preg_match_all('/\bGR-(CORE|TEST|UI|SEC)-[A-Z0-9]{3,}\b/', $content, $guardMatches);
            foreach (array_unique($guardMatches[0]) as $id) {
                if (!isset($knownGuardIds[$id])) {
                    $unknown[] = $relativePath . ': ' . $id;
                }
            }

            preg_match_all('/\bQG-[0-9]{3}\b/', $content, $gateMatches);
            foreach (array_unique($gateMatches[0]) as $id) {
                if (!isset($knownGateIds[$id])) {
                    $unknown[] = $relativePath . ': ' . $id;
                }
            }
        }

        $unknown = array_values(array_unique($unknown));
        sort($unknown);

        $this->assertSame(
            [],
            $unknown,
            "Unknown GR/QG references found in repository:\n" . implode("\n", $unknown)
        );
    }

    /**
     * @return list<string>
     */
    private function idReferenceScanFiles(): array
    {
        $root = $this->projectRootPath();
        $scanTargets = ['CLAUDE.md', '.agents', 'docs', 'tests', 'bin', '.gitea'];
        $extensions = ['md', 'json', 'php', 'sh', 'yml', 'yaml'];

        $files = [];
        foreach ($scanTargets as $target) {
            $fullPath = $root . '/' . $target;
            if (is_file($fullPath)) {
                $files[] = $target;
                continue;
            }
            if (!is_dir($fullPath)) {
                continue;
            }

            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($fullPath, \FilesystemIterator::SKIP_DOTS)
            );
            foreach ($iterator as $file) {
                if (!$file->isFile()) {
                    continue;
                }

                $ext = strtolower((string) $file->getExtension());
                if (!in_array($ext, $extensions, true)) {
                    continue;
                }

                $relativePath = str_replace($root . '/', '', $file->getPathname());
                $files[] = $relativePath;
            }
        }

        $files = array_values(array_unique($files));
        sort($files);
        return $files;
    }
}
