<?php

namespace MintyPHP\Tests\App\Module;

use InvalidArgumentException;
use MintyPHP\App\Module\ModuleManifest;
use PHPUnit\Framework\TestCase;

final class ModuleManifestTest extends TestCase
{
    public function testMinimalManifest(): void
    {
        $manifest = ModuleManifest::fromArray(['id' => 'testmod'], '/tmp/modules/testmod');

        self::assertSame('testmod', $manifest->id);
        self::assertSame('0.0.0', $manifest->version);
        self::assertFalse($manifest->enabledByDefault);
        self::assertSame(100, $manifest->loadOrder);
        self::assertSame([], $manifest->routes);
        self::assertSame([], $manifest->permissions);
        self::assertSame([], $manifest->searchResources);
        self::assertNull($manifest->migrationsPath);
    }

    public function testFullManifest(): void
    {
        $raw = [
            'id' => 'addressbook',
            'version' => '1.0.0',
            'enabled_by_default' => true,
            'load_order' => 10,
            'routes' => [['path' => '/address-book', 'target' => 'address-book/index']],
            'public_paths' => [],
            'container_registrars' => ['App\\Registrar'],
            'ui_slots' => ['aside.tab_panel' => [['key' => 'people', 'label' => 'People']]],
            'search_resources' => ['App\\SearchProvider'],
            'asset_groups' => ['address-book' => ['css/view.css']],
            'scheduler_jobs' => [
                [
                    'job_key' => 'addressbook.sync',
                    'handler' => 'App\\Scheduler\\AddressBookSyncHandler',
                    'label' => 'Address book sync',
                    'description' => 'Syncs address data',
                    'default_enabled' => true,
                    'default_timezone' => 'Europe/Berlin',
                    'default_schedule_type' => 'daily',
                    'default_schedule_interval' => 1,
                    'default_schedule_time' => '02:00',
                    'default_schedule_weekdays_csv' => null,
                    'default_catchup_once' => true,
                    'allowed_schedule_types' => ['daily', 'weekly'],
                ],
            ],
            'layout_context_providers' => ['App\\LayoutProvider'],
            'session_providers' => ['App\\SessionProvider'],
            'permissions' => [
                [
                    'key' => 'address_book.view',
                    'description' => 'Can view address book',
                    'active' => true,
                    'is_system' => true,
                ],
            ],
            'migrations_path' => 'db/updates',
        ];

        $manifest = ModuleManifest::fromArray($raw, '/srv/modules/addressbook');

        self::assertSame('addressbook', $manifest->id);
        self::assertSame('1.0.0', $manifest->version);
        self::assertTrue($manifest->enabledByDefault);
        self::assertSame(10, $manifest->loadOrder);
        self::assertCount(1, $manifest->routes);
        self::assertSame([[
            'key' => 'address_book.view',
            'description' => 'Can view address book',
            'active' => 1,
            'is_system' => 1,
        ]], $manifest->permissions);
        self::assertSame('addressbook.sync', $manifest->schedulerJobs[0]['job_key']);
        self::assertSame('/srv/modules/addressbook/db/updates', $manifest->migrationsPath);
        self::assertSame(['App\\LayoutProvider'], $manifest->layoutContextProviders);
        self::assertSame(['App\\SessionProvider'], $manifest->sessionProviders);
    }

    public function testMissingIdThrows(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('non-empty string "id"');

        ModuleManifest::fromArray([], '/tmp/modules/broken');
    }

    public function testEmptyIdThrows(): void
    {
        $this->expectException(InvalidArgumentException::class);

        ModuleManifest::fromArray(['id' => '  '], '/tmp/modules/broken');
    }

    public function testInvalidPermissionContractThrows(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage("must define non-empty 'key' and 'description'");

        ModuleManifest::fromArray([
            'id' => 'mod-invalid-perm',
            'permissions' => [['key' => 'mod.permission']],
        ], '/tmp/modules/mod-invalid-perm');
    }

    public function testInvalidSchedulerJobContractThrows(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage("missing required key 'allowed_schedule_types'");

        ModuleManifest::fromArray([
            'id' => 'mod-invalid-job',
            'scheduler_jobs' => [[
                'job_key' => 'mod.job',
                'handler' => 'App\\Handler',
                'label' => 'Label',
                'description' => 'Desc',
                'default_enabled' => true,
                'default_timezone' => 'UTC',
                'default_schedule_type' => 'daily',
                'default_schedule_interval' => 1,
                'default_schedule_time' => '01:00',
                'default_schedule_weekdays_csv' => null,
                'default_catchup_once' => true,
            ]],
        ], '/tmp/modules/mod-invalid-job');
    }
}
