<?php

namespace MintyPHP\Tests\Architecture;

use MintyPHP\App\Module\ModuleRegistry;
use PHPUnit\Framework\TestCase;

/**
 * Architecture test: verifies module registry contract in both module-on and module-off modes.
 */
final class ModuleRegistryContractTest extends TestCase
{
    public function testRegistryIsAvailableInContainer(): void
    {
        $registry = app(ModuleRegistry::class);

        self::assertInstanceOf(ModuleRegistry::class, $registry);
    }

    public function testAddressBookModuleLoadsWhenEnabled(): void
    {
        $modulesDir = dirname(__DIR__, 2) . '/modules';
        $registry = ModuleRegistry::boot($modulesDir, ['addressbook']);

        self::assertTrue($registry->hasModule('addressbook'));
        self::assertNotEmpty($registry->getSearchResources());
        self::assertNotEmpty($registry->getLayoutContextProviders());
        self::assertNotEmpty($registry->getSessionProviders());
    }

    public function testModuleOffModeReturnsEmptyRegistry(): void
    {
        $modulesDir = dirname(__DIR__, 2) . '/modules';

        putenv('APP_ENABLED_MODULES=');
        try {
            $enabledModules = ModuleRegistry::resolveEnabledModules([
                'enabled_modules' => ['addressbook', 'bookmarks'],
            ]);
            self::assertSame([], $enabledModules, 'APP_ENABLED_MODULES empty string must disable all modules.');
            $registry = ModuleRegistry::boot($modulesDir, $enabledModules);
        } finally {
            putenv('APP_ENABLED_MODULES');
        }

        self::assertSame([], $registry->getModules());
        self::assertSame([], $registry->getRoutes());
        self::assertSame([], $registry->getPermissions());
        self::assertSame([], $registry->getSearchResources());
        self::assertSame([], $registry->getLayoutContextProviders());
        self::assertSame([], $registry->getSessionProviders());
        self::assertSame([], $registry->getSlotContributions('aside.tab_panel'));
    }

    public function testAddressBookModuleManifestIsValid(): void
    {
        $modulesDir = dirname(__DIR__, 2) . '/modules';
        $manifestFile = $modulesDir . '/addressbook/module.php';

        self::assertFileExists($manifestFile, 'Address book module manifest must exist');

        $raw = require $manifestFile;
        self::assertIsArray($raw);
        self::assertSame('addressbook', $raw['id']);
        self::assertSame([[
            'key' => 'address_book.view',
            'description' => 'Can view address book',
            'active' => true,
            'is_system' => true,
        ]], $raw['permissions']);
        self::assertArrayHasKey('address-book', $raw['asset_groups']);
        self::assertSame(
            ['modules/addressbook/css/pages/address-book-view.css'],
            $raw['asset_groups']['address-book']
        );
        self::assertNotEmpty($raw['search_resources']);
        self::assertNotEmpty($raw['layout_context_providers']);
        self::assertNotEmpty($raw['session_providers']);
        self::assertArrayHasKey('aside.tab_panel', $raw['ui_slots']);
        self::assertArrayHasKey('search.resource_item', $raw['ui_slots']);
        self::assertArrayHasKey('user.edit.aside_action', $raw['ui_slots']);

        $routes = is_array($raw['routes'] ?? null) ? $raw['routes'] : [];
        $routeByPath = [];
        foreach ($routes as $route) {
            if (!is_array($route)) {
                continue;
            }
            $routeByPath[(string) ($route['path'] ?? '')] = (string) ($route['target'] ?? '');
        }
        self::assertSame('address-book', $routeByPath['address-book'] ?? null);
        self::assertSame('address-book/data', $routeByPath['address-book/data'] ?? null);
        self::assertSame('address-book', $routeByPath['addressbook'] ?? null);
        self::assertSame('address-book', $routeByPath['adressbook'] ?? null);
    }

    public function testBookmarksModuleManifestIsValid(): void
    {
        $modulesDir = dirname(__DIR__, 2) . '/modules';
        $manifestFile = $modulesDir . '/bookmarks/module.php';

        self::assertFileExists($manifestFile, 'Bookmarks module manifest must exist');

        $raw = require $manifestFile;
        self::assertIsArray($raw);
        self::assertSame('bookmarks', $raw['id']);
        self::assertArrayHasKey('aside.tab_panel', $raw['ui_slots']);
        self::assertArrayHasKey('topbar.right_item', $raw['ui_slots']);
        self::assertArrayHasKey('layout.body_end_template', $raw['ui_slots']);
        self::assertArrayHasKey('layout.head_style', $raw['ui_slots']);
        self::assertArrayHasKey('runtime.component', $raw['ui_slots']);
    }

    public function testModuleProviderClassesExist(): void
    {
        self::assertTrue(
            class_exists(\MintyPHP\Module\AddressBook\Providers\AddressBookSearchProvider::class),
            'AddressBookSearchProvider class must be autoloadable'
        );
        self::assertTrue(
            class_exists(\MintyPHP\Module\AddressBook\Providers\AddressBookLayoutProvider::class),
            'AddressBookLayoutProvider class must be autoloadable'
        );
        self::assertTrue(
            class_exists(\MintyPHP\Module\AddressBook\Providers\AddressBookSessionProvider::class),
            'AddressBookSessionProvider class must be autoloadable'
        );
    }

    public function testProviderClassesImplementContracts(): void
    {
        self::assertInstanceOf(
            \MintyPHP\App\Module\Contracts\SearchResourceProvider::class,
            new \MintyPHP\Module\AddressBook\Providers\AddressBookSearchProvider()
        );
        self::assertInstanceOf(
            \MintyPHP\App\Module\Contracts\LayoutContextProvider::class,
            new \MintyPHP\Module\AddressBook\Providers\AddressBookLayoutProvider()
        );
        self::assertInstanceOf(
            \MintyPHP\App\Module\Contracts\SessionProvider::class,
            new \MintyPHP\Module\AddressBook\Providers\AddressBookSessionProvider()
        );
    }

    public function testMissingDependencyThrowsException(): void
    {
        $fixturesDir = sys_get_temp_dir() . '/module-dep-test-' . uniqid();
        mkdir($fixturesDir . '/mod-dep', 0777, true);

        file_put_contents($fixturesDir . '/mod-dep/module.php', '<?php return ' . var_export([
            'id' => 'mod-dep',
            'requires' => ['nonexistent-module'],
        ], true) . ';');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage("requires module 'nonexistent-module' which is not enabled");

        try {
            ModuleRegistry::boot($fixturesDir, ['mod-dep']);
        } finally {
            @unlink($fixturesDir . '/mod-dep/module.php');
            @rmdir($fixturesDir . '/mod-dep');
            @rmdir($fixturesDir);
        }
    }

    public function testCircularDependencyThrowsException(): void
    {
        $fixturesDir = sys_get_temp_dir() . '/module-circular-test-' . uniqid();
        mkdir($fixturesDir . '/mod-x', 0777, true);
        mkdir($fixturesDir . '/mod-y', 0777, true);

        file_put_contents($fixturesDir . '/mod-x/module.php', '<?php return ' . var_export([
            'id' => 'mod-x',
            'requires' => ['mod-y'],
        ], true) . ';');
        file_put_contents($fixturesDir . '/mod-y/module.php', '<?php return ' . var_export([
            'id' => 'mod-y',
            'requires' => ['mod-x'],
        ], true) . ';');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Circular module dependency');

        try {
            ModuleRegistry::boot($fixturesDir, ['mod-x', 'mod-y']);
        } finally {
            @unlink($fixturesDir . '/mod-x/module.php');
            @unlink($fixturesDir . '/mod-y/module.php');
            @rmdir($fixturesDir . '/mod-x');
            @rmdir($fixturesDir . '/mod-y');
            @rmdir($fixturesDir);
        }
    }

    public function testConflictDetectionIsStrict(): void
    {
        $fixturesDir = sys_get_temp_dir() . '/module-arch-test-' . uniqid();
        mkdir($fixturesDir . '/mod-a', 0777, true);
        mkdir($fixturesDir . '/mod-b', 0777, true);

        file_put_contents($fixturesDir . '/mod-a/module.php', '<?php return ' . var_export([
            'id' => 'mod-a',
            'permissions' => [['key' => 'shared.perm', 'description' => 'Shared']],
        ], true) . ';');
        file_put_contents($fixturesDir . '/mod-b/module.php', '<?php return ' . var_export([
            'id' => 'mod-b',
            'permissions' => [['key' => 'shared.perm', 'description' => 'Shared duplicate']],
        ], true) . ';');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Permission conflict');

        try {
            ModuleRegistry::boot($fixturesDir, ['mod-a', 'mod-b']);
        } finally {
            array_map('unlink', glob($fixturesDir . '/mod-a/*') ?: []);
            array_map('unlink', glob($fixturesDir . '/mod-b/*') ?: []);
            rmdir($fixturesDir . '/mod-a');
            rmdir($fixturesDir . '/mod-b');
            rmdir($fixturesDir);
        }
    }
}
