<?php

namespace MintyPHP\Tests\Architecture;

final class ModuleManifestContractTest extends AbstractModuleStructureContractTestCase
{
    public function testAtLeastOneModuleExists(): void
    {
        self::assertNotEmpty(self::$modules, 'Expected at least one module under modules/');
    }

    public function testEveryModuleHasManifest(): void
    {
        $modulesDir = dirname(__DIR__, 2) . '/modules';
        if (!is_dir($modulesDir)) {
            self::markTestSkipped('No modules/ directory');
        }

        $entries = scandir($modulesDir) ?: [];
        foreach ($entries as $entry) {
            if ($entry === '.' || $entry === '..' || !is_dir($modulesDir . '/' . $entry)) {
                continue;
            }
            self::assertFileExists(
                $modulesDir . '/' . $entry . '/module.php',
                "Module directory '{$entry}' is missing module.php manifest"
            );
        }
    }

    public function testManifestIdMatchesDirectoryName(): void
    {
        foreach (self::$modules as $module) {
            self::assertSame(
                $module['id'],
                $module['manifest']->id,
                "Module directory '{$module['id']}' has mismatched manifest id '{$module['manifest']->id}'"
            );
        }
    }

    public function testManifestHasVersion(): void
    {
        foreach (self::$modules as $module) {
            self::assertMatchesRegularExpression(
                '/^\d+\.\d+\.\d+$/',
                $module['manifest']->version,
                "Module '{$module['id']}' must have a semver version (got '{$module['manifest']->version}')"
            );
        }
    }

    public function testManifestHasAllRequiredKeys(): void
    {
        $requiredKeys = [
            'id', 'version', 'enabled_by_default', 'load_order',
            'routes', 'public_paths', 'container_registrars',
            'ui_slots', 'search_resources', 'asset_groups',
            'scheduler_jobs', 'layout_context_providers',
            'session_providers', 'permissions',
        ];

        foreach (self::$modules as $module) {
            foreach ($requiredKeys as $key) {
                self::assertArrayHasKey(
                    $key,
                    $module['raw'],
                    "Module '{$module['id']}' manifest is missing required key '{$key}'"
                );
            }
        }
    }

    public function testRequiresFieldContainsOnlyValidModuleIds(): void
    {
        foreach (self::$modules as $module) {
            foreach ($module['manifest']->requires as $index => $requiredId) {
                self::assertNotEmpty(
                    trim($requiredId),
                    "Module '{$module['id']}' requires[{$index}] must be a non-empty string"
                );
                self::assertMatchesRegularExpression(
                    '/^[a-z][a-z0-9_-]*$/',
                    $requiredId,
                    "Module '{$module['id']}' requires[{$index}] has invalid module id format: '{$requiredId}'"
                );
            }
        }

        self::addToAssertionCount(1);
    }

    public function testModuleHasLibDirectory(): void
    {
        foreach (self::$modules as $module) {
            self::assertDirectoryExists(
                $module['path'] . '/lib',
                "Module '{$module['id']}' must have a lib/ directory for PHP classes"
            );
        }
    }

    public function testRoutesHavePathAndTarget(): void
    {
        foreach (self::$modules as $module) {
            foreach ($module['manifest']->routes as $idx => $route) {
                self::assertArrayHasKey(
                    'path',
                    $route,
                    "Module '{$module['id']}' route[{$idx}] is missing 'path'"
                );
                self::assertArrayHasKey(
                    'target',
                    $route,
                    "Module '{$module['id']}' route[{$idx}] is missing 'target'"
                );
                self::assertNotEmpty(
                    trim((string) $route['path']),
                    "Module '{$module['id']}' route[{$idx}] has empty 'path'"
                );
                self::assertNotEmpty(
                    trim((string) $route['target']),
                    "Module '{$module['id']}' route[{$idx}] has empty 'target'"
                );
            }
        }
    }

    public function testModulesWithRoutesHavePagesDirectory(): void
    {
        foreach (self::$modules as $module) {
            if (empty($module['manifest']->routes)) {
                continue;
            }
            self::assertDirectoryExists(
                $module['path'] . '/pages',
                "Module '{$module['id']}' declares routes but has no pages/ directory"
            );
        }
    }
}
