<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\TestCase;

/**
 * Views (.phtml) MUST use e() for HTML output — raw echo is forbidden (GR-SEC-010).
 *
 * Allowed exceptions (must be marked with an inline comment):
 *   <?php echo $var; ?> // raw-html-ok: reason
 *
 * Typical justified uses: pre-escaped ARIA attributes from navActive(), hardcoded
 * HTML-safe ternaries (e.g. 'aria-current="page"'), or pre-built HTML fragments
 * from shared helpers. Each raw echo MUST carry the "raw-html-ok" marker.
 */
class ViewLayerOutputEscapingContractTest extends TestCase
{
    use ProjectFileAssertionSupport;

    /**
     * Regex matching <?php echo (with optional whitespace variations).
     * Does NOT match <?php e( which is the escaped helper.
     */
    private const ECHO_PATTERN = '/<\?php\s+echo\b/';

    /** Inline marker that excuses a raw echo on the same line. */
    private const EXCEPTION_MARKER = 'raw-html-ok';

    public function testCorePagesViewsUseEscapedOutput(): void
    {
        $violations = $this->scanForRawEcho('pages');
        self::assertSame(
            [],
            $violations,
            "Raw <?php echo found in core pages views without '" . self::EXCEPTION_MARKER . "' marker:\n" . implode("\n", $violations),
        );
    }

    public function testCoreTemplatesUseEscapedOutput(): void
    {
        $violations = $this->scanForRawEcho('templates');
        self::assertSame(
            [],
            $violations,
            "Raw <?php echo found in core templates without '" . self::EXCEPTION_MARKER . "' marker:\n" . implode("\n", $violations),
        );
    }

    public function testModuleViewsUseEscapedOutput(): void
    {
        $root = $this->projectRootPath();
        $modulesDir = $root . '/modules';
        if (!is_dir($modulesDir)) {
            self::markTestSkipped('modules/ directory missing.');
        }

        $allViolations = [];
        foreach (scandir($modulesDir) ?: [] as $entry) {
            if ($entry === '.' || $entry === '..' || !is_dir($modulesDir . '/' . $entry)) {
                continue;
            }
            foreach (['pages', 'templates'] as $subDir) {
                $rel = 'modules/' . $entry . '/' . $subDir;
                if (is_dir($root . '/' . $rel)) {
                    $allViolations = array_merge($allViolations, $this->scanForRawEcho($rel));
                }
            }
        }

        sort($allViolations);
        self::assertSame(
            [],
            $allViolations,
            "Raw <?php echo found in module views without '" . self::EXCEPTION_MARKER . "' marker:\n" . implode("\n", $allViolations),
        );
    }

    /**
     * Scan .phtml files for raw echo statements, returning violations as "file:line: content".
     *
     * @return list<string>
     */
    private function scanForRawEcho(string $relativeDirectory): array
    {
        $root = $this->projectRootPath();
        $basePath = $root . '/' . $relativeDirectory;
        $this->assertDirectoryExists($basePath, 'Directory not found: ' . $relativeDirectory);

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($basePath),
        );

        $violations = [];

        /** @var \SplFileInfo $file */
        foreach ($iterator as $file) {
            if (!$file->isFile() || $file->getExtension() !== 'phtml') {
                continue;
            }

            $content = file_get_contents($file->getPathname());
            $this->assertNotFalse($content, 'Could not read file: ' . $file->getPathname());

            $lines = explode("\n", $content);
            foreach ($lines as $lineNumber => $line) {
                if (!preg_match(self::ECHO_PATTERN, $line)) {
                    continue;
                }

                if (str_contains($line, self::EXCEPTION_MARKER)) {
                    continue;
                }

                $relativePath = str_replace($root . '/', '', $file->getPathname());
                $violations[] = sprintf('%s:%d: %s', $relativePath, $lineNumber + 1, trim($line));
            }
        }

        sort($violations);
        return $violations;
    }
}
