<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\TestCase;

class CodexSkillsContractTest extends TestCase
{
    use ProjectFileAssertionSupport;

    public function testCoreGuardrailsSkillAndReferencesExist(): void
    {
        $skill = $this->readProjectFile('tools/codex-skills/core-guardrails/SKILL.md');
        $this->assertStringContainsString('name: core-guardrails', $skill);

        $this->readProjectFile('tools/codex-skills/core-guardrails/references/boundaries-core.md');
        $this->readProjectFile('tools/codex-skills/core-guardrails/references/boundaries-ui.md');
        $this->readProjectFile('tools/codex-skills/core-guardrails/references/quality-gates.md');
        $this->readProjectFile('tools/codex-skills/core-guardrails/references/source-map.md');
    }

    public function testStarterkitPlannerSkillAndReferencesExist(): void
    {
        $skill = $this->readProjectFile('tools/codex-skills/starterkit-planner/SKILL.md');
        $this->assertStringContainsString('name: starterkit-planner', $skill);

        $this->readProjectFile('tools/codex-skills/starterkit-planner/references/plan-template.md');
        $this->readProjectFile('tools/codex-skills/starterkit-planner/references/tradeoff-matrix.md');
    }

    public function testStarterkitGridStandardsSkillAndReferencesExist(): void
    {
        $skill = $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/SKILL.md');
        $this->assertStringContainsString('name: starterkit-grid-standards', $skill);

        $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/references/list-checklist.md');
        $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/references/row-interaction-standard.md');
        $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/references/page-size-standard.md');
    }

    public function testStarterkitPhpStyleCiSkillExists(): void
    {
        $skill = $this->readProjectFile('tools/codex-skills/starterkit-php-style-ci/SKILL.md');
        $this->assertStringContainsString('name: starterkit-php-style-ci', $skill);
    }

    public function testCodexPromptsDocumentationReferencesBothSkills(): void
    {
        $prompts = $this->readProjectFile('docs/reference-codex-prompts.md');
        $this->assertStringContainsString('core-guardrails', $prompts);
        $this->assertStringContainsString('starterkit-planner', $prompts);
    }

    public function testDocsIndexReferencesCodexPrompts(): void
    {
        $index = $this->readProjectFile('docs/index.md');
        $this->assertStringContainsString('/docs/reference-codex-prompts.md', $index);
    }

    public function testCodexSkillsSyncScriptSupportsCheckAndApply(): void
    {
        $script = $this->readProjectFile('bin/codex-skills-sync.sh');
        $this->assertStringContainsString('--check', $script);
        $this->assertStringContainsString('--apply', $script);
        $this->assertStringContainsString('mode="check"', $script);
        $this->assertStringContainsString('"core-guardrails"', $script);
        $this->assertStringContainsString('"starterkit-planner"', $script);
        $this->assertStringContainsString('"starterkit-grid-standards"', $script);
        $this->assertStringContainsString('"starterkit-php-style-ci"', $script);
    }

    public function testDocsLinkCheckScriptExcludesHistoricalRunArtifactsByDefault(): void
    {
        $script = $this->readProjectFile('bin/docs-link-check.sh');
        $this->assertStringContainsString("!agent-system/runs/**", $script);
    }

    public function testDocumentationLinksResolveAcrossDocsSkillsAndAgentGuides(): void
    {
        foreach ($this->filesCoveredByDocsLinkIntegrityCheck() as $file) {
            $content = $this->readProjectFile($file);
            preg_match_all('/(?<![A-Za-z0-9_])\\/?docs\\/[a-z0-9_\\/-]+\\.md\\b/', $content, $matches);
            foreach (array_unique($matches[0]) as $link) {
                $normalized = ltrim($link, '/');
                $absolute = $this->projectRootPath() . '/' . $normalized;
                $this->assertFileExists($absolute, sprintf('Broken docs link "%s" in %s', $link, $file));
            }
        }
    }

    public function testDocsIndexCoversAllMarkdownDocsWithoutOrphansOrDuplicates(): void
    {
        $index = $this->readProjectFile('docs/index.md');
        preg_match_all('/\\/docs\\/([a-z0-9-]+\\.md)\\b/', $index, $matches);
        $references = $matches[1];

        $docFiles = glob($this->projectRootPath() . '/docs/*.md');
        $this->assertNotFalse($docFiles, 'Failed to enumerate docs/*.md files.');

        $expectedDocs = [];
        foreach ($docFiles as $fullPath) {
            $basename = basename($fullPath);
            if ($basename !== 'index.md') {
                $expectedDocs[] = $basename;
            }
        }

        $refCounts = array_count_values($references);
        $duplicates = [];
        foreach ($refCounts as $ref => $count) {
            if ($count > 1) {
                $duplicates[] = $ref;
            }
        }

        $uniqueReferences = array_values(array_unique($references));
        sort($expectedDocs);
        sort($uniqueReferences);
        sort($duplicates);

        $orphans = array_values(array_diff($expectedDocs, $uniqueReferences));
        $missingTargets = array_values(array_diff($uniqueReferences, $expectedDocs));

        $this->assertSame([], $duplicates, 'Duplicate docs/index.md references: ' . implode(', ', $duplicates));
        $this->assertSame([], $orphans, 'Docs markdown files missing in docs/index.md: ' . implode(', ', $orphans));
        $this->assertSame([], $missingTargets, 'docs/index.md references missing files: ' . implode(', ', $missingTargets));
    }

    public function testQualityGatesIncludeDocsLinksAndSkillsSyncChecks(): void
    {
        $qualityGates = json_decode($this->readProjectFile('agent-system/checks/quality-gates.json'), true);
        $this->assertIsArray($qualityGates, 'Invalid quality-gates.json');
        $gates = is_array($qualityGates['gates'] ?? null) ? $qualityGates['gates'] : [];

        $byId = [];
        foreach ($gates as $gate) {
            $id = (string) ($gate['id'] ?? '');
            $byId[$id] = $gate;
        }

        $this->assertArrayHasKey('QG-008', $byId);
        $this->assertArrayHasKey('QG-009', $byId);
        $this->assertSame('bin/docs-link-check.sh', (string) ($byId['QG-008']['command'] ?? ''));
        $this->assertSame('bin/codex-skills-sync.sh --check', (string) ($byId['QG-009']['command'] ?? ''));
    }

    public function testDocsSkillsAuditPolicyExists(): void
    {
        $policy = $this->readProjectFile('agent-system/checks/docs-skills-audit-policy.md');
        $this->assertStringContainsString('P0', $policy);
        $this->assertStringContainsString('P1', $policy);
        $this->assertStringContainsString('DOCS-SKILLS-AUDIT-00x', $policy);
    }

    public function testReviewerGuardsFlowEnforcesDocsSkillsAuditPolicy(): void
    {
        $prompt = $this->readProjectFile('agent-system/prompts/reviewer-guards.md');
        $schema = $this->readProjectFile('agent-system/contracts/reviewer-guards.schema.json');

        $this->assertStringContainsString('agent-system/checks/docs-skills-audit-policy.md', $prompt);
        $this->assertStringContainsString('DOCS-SKILLS-AUDIT', $prompt);

        $this->assertStringContainsString('audit_policy_ref', $schema);
        $this->assertStringContainsString('"P0"', $schema);
        $this->assertStringContainsString('"P1"', $schema);
        $this->assertStringContainsString('run_artifact_id', $schema);
        $this->assertStringContainsString('DOCS-SKILLS-AUDIT-[0-9]{3}', $schema);
    }

    /**
     * @return list<string>
     */
    private function filesCoveredByDocsLinkIntegrityCheck(): array
    {
        $root = $this->projectRootPath();
        $files = ['README.md'];
        $scanDirs = ['docs', 'tools/codex-skills', 'agent-system'];

        foreach ($scanDirs as $scanDir) {
            $directory = new \RecursiveDirectoryIterator(
                $root . '/' . $scanDir,
                \FilesystemIterator::SKIP_DOTS
            );
            $iterator = new \RecursiveIteratorIterator($directory);

            foreach ($iterator as $fileInfo) {
                if (!$fileInfo->isFile()) {
                    continue;
                }

                $fullPath = str_replace('\\', '/', $fileInfo->getPathname());
                if (!str_ends_with($fullPath, '.md')) {
                    continue;
                }

                $relative = ltrim(str_replace(str_replace('\\', '/', $root), '', $fullPath), '/');
                if (str_starts_with($relative, 'agent-system/runs/')) {
                    continue;
                }

                $files[] = $relative;
            }
        }

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