<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\TestCase;

class AgentSystemContractTest extends TestCase
{
    use ProjectFileAssertionSupport;

    public function testAgentSystemJsonFilesAreValidJson(): void
    {
        $jsonFiles = [
            '.agents/checks/guard-catalog.json',
            '.agents/checks/quality-gates.json',
            '.agents/contracts/analyst.schema.json',
            '.agents/contracts/planner.schema.json',
            '.agents/contracts/executor.schema.json',
            '.agents/contracts/reviewer-code.schema.json',
            '.agents/contracts/reviewer-security.schema.json',
            '.agents/contracts/reviewer-acceptance.schema.json',
            '.agents/contracts/finalizer.schema.json',
            '.agents/templates/analysis.template.json',
            '.agents/templates/plan.template.json',
            '.agents/templates/execution-report.template.json',
            '.agents/templates/review-code.template.json',
            '.agents/templates/review-security.template.json',
            '.agents/templates/review-acceptance.template.json',
            '.agents/templates/finalize.template.json',
            '.agents/contracts/module-manifest.schema.json',
        ];

        foreach ($jsonFiles as $file) {
            $decoded = json_decode($this->readProjectFile($file), true);
            $this->assertIsArray($decoded, 'Invalid JSON: ' . $file);
        }
    }

    public function testGuardCatalogIdsAreUniqueAndWellFormed(): void
    {
        $catalog = $this->decodeJson('.agents/checks/guard-catalog.json');
        $guards = is_array($catalog['guards'] ?? null) ? $catalog['guards'] : [];
        $this->assertNotSame([], $guards, 'Guard catalog must not be empty.');

        $ids = [];
        foreach ($guards as $guard) {
            $id = (string) ($guard['id'] ?? '');
            $reviewer = (string) ($guard['reviewer'] ?? '');
            $this->assertMatchesRegularExpression('/^GR-(CORE|TEST|UI|SEC)-[A-Z0-9]{3,}$/', $id, 'Invalid guard id: ' . $id);
            $this->assertContains($reviewer, ['code', 'security'], 'Invalid reviewer for ' . $id);
            $ids[] = $id;
        }

        $this->assertSame($ids, array_values(array_unique($ids)), 'Duplicate guard IDs found.');
    }

    public function testGuardCatalogReviewerAssignment(): void
    {
        $catalog = $this->decodeJson('.agents/checks/guard-catalog.json');
        $guards = is_array($catalog['guards'] ?? null) ? $catalog['guards'] : [];

        foreach ($guards as $guard) {
            $id = (string) ($guard['id'] ?? '');
            $reviewer = (string) ($guard['reviewer'] ?? '');

            if (str_starts_with($id, 'GR-SEC-')) {
                $this->assertSame('security', $reviewer, $id . ' must be assigned to security reviewer');
            } else {
                $this->assertSame('code', $reviewer, $id . ' must be assigned to code reviewer');
            }
        }
    }

    public function testQualityGateIdsAreUniqueAndWellFormed(): void
    {
        $gates = $this->decodeJson('.agents/checks/quality-gates.json');
        $items = is_array($gates['gates'] ?? null) ? $gates['gates'] : [];
        $this->assertNotSame([], $items, 'Quality gates must not be empty.');

        $ids = [];
        foreach ($items as $gate) {
            $id = (string) ($gate['id'] ?? '');
            $command = (string) ($gate['command'] ?? '');
            $this->assertMatchesRegularExpression('/^QG-[0-9]{3}$/', $id, 'Invalid quality gate id: ' . $id);
            $this->assertNotSame('', trim($command), 'Missing command for ' . $id);
            $ids[] = $id;
        }

        $this->assertSame($ids, array_values(array_unique($ids)), 'Duplicate quality gate IDs found.');
    }

    public function testTemplatesReferenceKnownGuardAndGateIds(): void
    {
        $catalog = $this->decodeJson('.agents/checks/guard-catalog.json');
        $gates = $this->decodeJson('.agents/checks/quality-gates.json');
        $guardIds = array_map(
            static fn (array $guard): string => (string) ($guard['id'] ?? ''),
            is_array($catalog['guards'] ?? null) ? $catalog['guards'] : []
        );
        $gateIds = array_map(
            static fn (array $gate): string => (string) ($gate['id'] ?? ''),
            is_array($gates['gates'] ?? null) ? $gates['gates'] : []
        );

        $planTemplate = $this->decodeJson('.agents/templates/plan.template.json');
        $planGuardIds = is_array($planTemplate['guardrails']['required_guard_ids'] ?? null)
            ? $planTemplate['guardrails']['required_guard_ids']
            : [];
        $planGateIds = is_array($planTemplate['guardrails']['required_quality_gate_ids'] ?? null)
            ? $planTemplate['guardrails']['required_quality_gate_ids']
            : [];
        foreach ($planGuardIds as $id) {
            $this->assertContains($id, $guardIds, 'Unknown guard id in plan template: ' . (string) $id);
        }
        foreach ($planGateIds as $id) {
            $this->assertContains($id, $gateIds, 'Unknown gate id in plan template: ' . (string) $id);
        }

        $reviewCodeTemplate = $this->decodeJson('.agents/templates/review-code.template.json');
        $reviewGuardIds = is_array($reviewCodeTemplate['checked_guard_ids'] ?? null)
            ? $reviewCodeTemplate['checked_guard_ids']
            : [];
        $reviewGateIds = is_array($reviewCodeTemplate['checked_quality_gate_ids'] ?? null)
            ? $reviewCodeTemplate['checked_quality_gate_ids']
            : [];
        foreach ($reviewGuardIds as $id) {
            $this->assertContains($id, $guardIds, 'Unknown guard id in review-code template: ' . (string) $id);
        }
        foreach ($reviewGateIds as $id) {
            $this->assertContains($id, $gateIds, 'Unknown gate id in review-code template: ' . (string) $id);
        }

        $reviewSecurityTemplate = $this->decodeJson('.agents/templates/review-security.template.json');
        $secGuardIds = is_array($reviewSecurityTemplate['checked_guard_ids'] ?? null)
            ? $reviewSecurityTemplate['checked_guard_ids']
            : [];
        foreach ($secGuardIds as $id) {
            $this->assertContains($id, $guardIds, 'Unknown guard id in review-security template: ' . (string) $id);
        }
    }

    public function testRolePromptsReferenceGuardCatalog(): void
    {
        $plannerPrompt = $this->readProjectFile('.agents/prompts/planner.md');
        $executorPrompt = $this->readProjectFile('.agents/prompts/executor.md');
        $codeReviewerPrompt = $this->readProjectFile('.agents/prompts/reviewer-code.md');
        $securityReviewerPrompt = $this->readProjectFile('.agents/prompts/reviewer-security.md');

        $this->assertStringContainsString('.agents/checks/guard-catalog.json', $plannerPrompt);
        $this->assertStringContainsString('.agents/checks/quality-gates.json', $plannerPrompt);
        $this->assertStringContainsString('.agents/checks/guard-catalog.json', $executorPrompt);
        $this->assertStringContainsString('.agents/checks/quality-gates.json', $executorPrompt);
        $this->assertStringContainsString('.agents/checks/guard-catalog.json', $codeReviewerPrompt);
        $this->assertStringContainsString('.agents/checks/guard-catalog.json', $securityReviewerPrompt);
    }

    public function testAllSevenRolePromptsExist(): void
    {
        $roles = ['analyst', 'planner', 'executor', 'reviewer-code', 'reviewer-security', 'reviewer-acceptance', 'finalizer'];

        foreach ($roles as $role) {
            $this->readProjectFile('.agents/prompts/' . $role . '.md');
        }
    }

    public function testAllSevenContractsExist(): void
    {
        $contracts = ['analyst', 'planner', 'executor', 'reviewer-code', 'reviewer-security', 'reviewer-acceptance', 'finalizer'];

        foreach ($contracts as $contract) {
            $decoded = json_decode($this->readProjectFile('.agents/contracts/' . $contract . '.schema.json'), true);
            $this->assertIsArray($decoded, 'Invalid JSON schema: ' . $contract);
        }
    }

    public function testAllSevenTemplatesExist(): void
    {
        $templates = [
            'analysis', 'plan', 'execution-report',
            'review-code', 'review-security', 'review-acceptance', 'finalize',
        ];

        foreach ($templates as $template) {
            $decoded = json_decode($this->readProjectFile('.agents/templates/' . $template . '.template.json'), true);
            $this->assertIsArray($decoded, 'Invalid JSON template: ' . $template);
        }
    }

    /**
     * @return array<string,mixed>
     */
    private function decodeJson(string $path): array
    {
        $decoded = json_decode($this->readProjectFile($path), true);
        $this->assertIsArray($decoded, 'Invalid JSON in ' . $path);
        return $decoded;
    }
}
