<?php

declare(strict_types=1);

namespace MintyPHP\Console\Runner\Module;

use MintyPHP\App\AppContainer;
use MintyPHP\App\Module\Contracts\ModuleDeactivationHandler;
use MintyPHP\App\Module\ModuleManifestLoader;
use MintyPHP\App\Module\ModulePermissionSynchronizer;
use MintyPHP\App\Module\ModuleRegistry;
use MintyPHP\App\Module\ModuleRuntimeAssetPublisher;
use MintyPHP\App\Module\ModuleRuntimePageBuilder;
use MintyPHP\Console\Support\CliAppBootstrap;
use MintyPHP\Console\Support\ModuleCliRuntime;
use MintyPHP\Service\Database\CoreMigrationService;
use MintyPHP\Service\Module\ModuleMigrationService;

final class ModuleRunner implements ModuleRunnerInterface
{
    public function dbMigrate(): array
    {
        $summary = ['applied' => 0, 'skipped_empty' => 0, 'total_files' => 0, 'applied_filenames' => []];
        $message = 'db-migrate: all updates already applied.';
        $step = ModuleCliRuntime::runStep('db-migrate', function () use (&$summary, &$message): int {
            $lockResult = ModuleCliRuntime::withFileLock(
                ModuleCliRuntime::projectRoot() . '/storage/runtime/.db-migrate.lock',
                'db-migrate: another migration run is in progress, skipping.',
                function () use (&$summary, &$message): int {
                    /** @var CoreMigrationService $service */
                    $service = app(CoreMigrationService::class);
                    $result = $service->applyPendingMigrations();
                    $summary = $result;

                    if ($result['total_files'] === 0) {
                        $message = 'db-migrate: no update files in db/updates/, nothing to do.';
                    } elseif ($result['applied'] === 0) {
                        $message = 'db-migrate: all updates already applied.';
                    } else {
                        $message = sprintf('db-migrate: applied %d migration(s).', $result['applied']);
                    }

                    if ($result['skipped_empty'] > 0) {
                        $message .= sprintf(' (%d empty file(s) skipped)', $result['skipped_empty']);
                    }

                    return $result['ok'] ? 0 : 1;
                }
            );

            if ($lockResult['busy']) {
                $message = (string) $lockResult['message'];
            }

            return $lockResult['exit_code'];
        });

        return [
            'command' => 'db:migrate',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored' => $step['vendor_warnings_ignored'],
            'message' => $step['error'] ?? $message,
        ];
    }

    public function dbMigrateStatus(): array
    {
        CliAppBootstrap::bootstrap('cli', 'bin/console db:migrate --status');
        /** @var CoreMigrationService $service */
        $service = app(CoreMigrationService::class);
        $status = $service->status();

        return [
            'command' => 'db:migrate --status',
            'applied' => $status['applied'],
            'pending' => $status['pending'],
        ];
    }

    public function migrate(): array
    {
        $summary = ['applied' => 0, 'modules_enabled' => 0, 'skipped_empty' => 0];
        $message = 'module-migrate: all modules up to date, 0 new migrations.';
        $step = ModuleCliRuntime::runStep('module-migrate', function () use (&$summary, &$message): int {
            $lockResult = ModuleCliRuntime::withFileLock(
                ModuleCliRuntime::projectRoot() . '/storage/runtime/.module-migrate.lock',
                'module-migrate: another migration run is in progress, skipping.',
                function () use (&$summary, &$message): int {
                    /** @var ModuleMigrationService $service */
                    $service = app(ModuleMigrationService::class);
                    $result = $service->applyPendingMigrations();
                    $summary = $result;

                    if ($result['modules_enabled'] === 0) {
                        $message = 'module-migrate: no modules enabled, nothing to do.';
                    } elseif ($result['applied'] === 0) {
                        $message = 'module-migrate: all modules up to date, 0 new migrations.';
                    } else {
                        $message = sprintf('module-migrate: applied %d migration(s).', $result['applied']);
                    }

                    if ($result['skipped_empty'] > 0) {
                        $message .= sprintf(' (%d empty file(s) skipped)', $result['skipped_empty']);
                    }

                    return $result['ok'] ? 0 : 1;
                }
            );

            if ($lockResult['busy']) {
                $message = (string) $lockResult['message'];
            }

            return $lockResult['exit_code'];
        });

        return [
            'command' => 'module:migrate',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored' => $step['vendor_warnings_ignored'],
            'message' => $step['error'] ?? $message,
        ];
    }

    public function permissionsSync(): array
    {
        $sync = [
            'created' => 0,
            'updated' => 0,
            'unchanged' => 0,
            'deactivated' => 0,
            'total' => 0,
        ];
        $message = 'module-permissions-sync: ok';

        $step = ModuleCliRuntime::runStep('module-permissions-sync', function () use (&$sync, &$message): int {
            $lockResult = ModuleCliRuntime::withFileLock(
                ModuleCliRuntime::projectRoot() . '/storage/runtime/.module-permissions-sync.lock',
                'module-permissions-sync: another sync is in progress, skipping.',
                function () use (&$sync): int {
                    /** @var ModulePermissionSynchronizer $synchronizer */
                    $synchronizer = app(ModulePermissionSynchronizer::class);
                    $sync = $synchronizer->sync();
                    return 0;
                }
            );

            if ($lockResult['busy']) {
                $message = (string) $lockResult['message'];
            }

            return $lockResult['exit_code'];
        });

        return [
            'command' => 'module:permissions-sync',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored' => $step['vendor_warnings_ignored'],
            'created' => (int) $sync['created'],
            'updated' => (int) $sync['updated'],
            'unchanged' => (int) $sync['unchanged'],
            'deactivated' => (int) $sync['deactivated'],
            'total' => (int) $sync['total'],
            'message' => $step['error'] ?? $message,
        ];
    }

    public function build(): array
    {
        $buildSummary = [
            'core_entries' => 0,
            'module_entries' => 0,
        ];
        $message = 'module-build: ok';

        $step = ModuleCliRuntime::runStep('module-build', function () use (&$buildSummary, &$message): int {
            $runtimeDir = ModuleCliRuntime::projectRoot() . '/storage/runtime/pages';
            $runtimeParent = dirname($runtimeDir);
            if (!is_dir($runtimeParent) && !mkdir($runtimeParent, 0775, true) && !is_dir($runtimeParent)) {
                $message = "Failed to create runtime directory: {$runtimeParent}";
                return 1;
            }

            $lockResult = ModuleCliRuntime::withFileLock(
                ModuleCliRuntime::projectRoot() . '/storage/runtime/.module-build.lock',
                'module-build: another build is in progress, skipping.',
                function () use (&$buildSummary, $runtimeDir): int {
                    /** @var ModuleRegistry $registry */
                    $registry = app(ModuleRegistry::class);
                    /** @var ModuleRuntimePageBuilder $builder */
                    $builder = app(ModuleRuntimePageBuilder::class);
                    $modules = $registry->getModules();
                    $buildSummary = $builder->build(
                        $runtimeDir,
                        ModuleCliRuntime::projectRoot() . '/pages',
                        $modules
                    );
                    ModuleRuntimePageBuilder::writeFingerprint($runtimeDir, $modules);
                    return 0;
                }
            );

            if ($lockResult['busy']) {
                $message = (string) $lockResult['message'];
            }

            return $lockResult['exit_code'];
        });

        return [
            'command' => 'module:build',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored' => $step['vendor_warnings_ignored'],
            'core_entries' => (int) $buildSummary['core_entries'],
            'module_entries' => (int) $buildSummary['module_entries'],
            'message' => $step['error'] ?? $message,
        ];
    }

    public function assetsSync(): array
    {
        $summary = [
            'created' => 0,
            'updated' => 0,
            'removed' => 0,
            'unchanged' => 0,
        ];
        $mode = trim((string) getenv('APP_MODULE_ASSET_MODE'));
        if ($mode === '') {
            $mode = 'symlink';
        }
        $message = 'module-assets-sync: ok';

        $step = ModuleCliRuntime::runStep('module-assets-sync', function () use (&$summary, &$message, $mode): int {
            $lockResult = ModuleCliRuntime::withFileLock(
                ModuleCliRuntime::projectRoot() . '/storage/runtime/.module-assets-sync.lock',
                'module-assets-sync: another sync is in progress, skipping.',
                function () use (&$summary, $mode): int {
                    /** @var ModuleRegistry $registry */
                    $registry = app(ModuleRegistry::class);
                    /** @var ModuleRuntimeAssetPublisher $publisher */
                    $publisher = app(ModuleRuntimeAssetPublisher::class);
                    $summary = $publisher->publish(
                        ModuleCliRuntime::projectRoot() . '/web/modules',
                        $registry->getModules(),
                        $mode
                    );
                    return 0;
                }
            );

            if ($lockResult['busy']) {
                $message = (string) $lockResult['message'];
            }

            return $lockResult['exit_code'];
        });

        return [
            'command' => 'module:assets-sync',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored' => $step['vendor_warnings_ignored'],
            'mode' => $mode,
            'created' => (int) $summary['created'],
            'updated' => (int) $summary['updated'],
            'removed' => (int) $summary['removed'],
            'unchanged' => (int) $summary['unchanged'],
            'message' => $step['error'] ?? $message,
        ];
    }

    public function deactivate(string $moduleId, bool $confirm, bool $dryRun): array
    {
        $message = '';

        $step = ModuleCliRuntime::runStep('module-deactivate', function () use ($moduleId, $confirm, $dryRun, &$message): int {
            if ($moduleId === '') {
                $message = 'Error: module id is required.';
                return 1;
            }

            if (!$confirm && !$dryRun) {
                $message = 'Error: must pass --confirm to execute or --dry-run to validate.';
                return 1;
            }

            $modulesDir = ModuleCliRuntime::projectRoot() . '/modules';
            $manifest = ModuleManifestLoader::loadFromDisk($modulesDir, $moduleId);

            // Check for reverse dependencies (other modules that require this one)
            $dependents = $this->findReverseDependencies($modulesDir, $moduleId);
            $warning = '';
            if ($dependents !== []) {
                $dependentList = implode(', ', $dependents);
                $warning = "Warning: module(s) [{$dependentList}] declare '{$moduleId}' in their requires — deactivation may break them.\n";
            }

            $handlerClass = $manifest->deactivationHandler;
            if ($handlerClass === null) {
                $message = $warning . "Module '{$moduleId}' has no deactivation_handler declared — nothing to do.";
                return 0;
            }

            if (!class_exists($handlerClass)) {
                $message = $warning . "Error: deactivation handler class '{$handlerClass}' not found.";
                return 1;
            }

            $container = $GLOBALS['minty_app_container'] ?? null;
            if (!$container instanceof AppContainer) {
                $message = $warning . 'Error: app container is not initialized.';
                return 1;
            }
            $handler = $container->has($handlerClass)
                ? $container->get($handlerClass)
                : new $handlerClass();

            if (!$handler instanceof ModuleDeactivationHandler) {
                $message = $warning . "Error: '{$handlerClass}' does not implement ModuleDeactivationHandler.";
                return 1;
            }

            if ($dryRun) {
                $message = $warning . "Dry-run: handler '{$handlerClass}' found and valid for module '{$moduleId}'.";
                return 0;
            }

            $handler->deactivate($container);
            $message = $warning . "Deactivation handler for module '{$moduleId}' completed.";

            return 0;
        });

        return [
            'command' => 'module:deactivate',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored' => $step['vendor_warnings_ignored'],
            'message' => $step['error'] ?? $message,
        ];
    }

    public function runtimeSync(): array
    {
        $startedAt = microtime(true);
        $steps = [
            'db-migrate' => ['status' => 'pending', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'migrate' => ['status' => 'pending', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'permissions-sync' => ['status' => 'pending', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'build' => ['status' => 'pending', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'assets-sync' => ['status' => 'pending', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
        ];
        $message = '';

        $step = ModuleCliRuntime::runStep('module-runtime-sync', function () use (&$steps, &$message): int {
            $lockResult = ModuleCliRuntime::withFileLock(
                ModuleCliRuntime::projectRoot() . '/storage/runtime/.module-runtime-sync.lock',
                'module-runtime-sync: another runtime sync is in progress, skipping.',
                function () use (&$steps): int {
                    $orderedSteps = [
                        'db-migrate' => fn (): array => $this->dbMigrate(),
                        'migrate' => fn (): array => $this->migrate(),
                        'permissions-sync' => fn (): array => $this->permissionsSync(),
                        'build' => fn (): array => $this->build(),
                        'assets-sync' => fn (): array => $this->assetsSync(),
                    ];

                    foreach ($orderedSteps as $stepName => $runner) {
                        $result = $runner();
                        $steps[$stepName] = [
                            'status' => $result['status'],
                            'exit_code' => $result['exit_code'],
                            'vendor_warnings_ignored' => $result['vendor_warnings_ignored'],
                        ];

                        if ($result['exit_code'] !== 0) {
                            return $result['exit_code'];
                        }
                    }

                    return 0;
                }
            );

            if ($lockResult['busy']) {
                $message = (string) $lockResult['message'];
            }

            return $lockResult['exit_code'];
        });

        if ($message === '') {
            $message = sprintf(
                'module-runtime-sync: summary db-migrate=%s migrate=%s permissions-sync=%s build=%s assets-sync=%s vendor_warnings_ignored=%d',
                $steps['db-migrate']['status'],
                $steps['migrate']['status'],
                $steps['permissions-sync']['status'],
                $steps['build']['status'],
                $steps['assets-sync']['status'],
                $step['vendor_warnings_ignored']
            );
        }

        return [
            'command' => 'module:sync',
            'status' => $step['status'],
            'exit_code' => $step['exit_code'],
            'vendor_warnings_ignored_total' => $step['vendor_warnings_ignored'],
            'duration_ms' => max(0, (int) round((microtime(true) - $startedAt) * 1000)),
            'steps' => $steps,
            'message' => $step['error'] ?? $message,
        ];
    }

    /**
     * @return list<string> Module IDs that declare $targetId in their requires
     */
    private function findReverseDependencies(string $modulesDir, string $targetId): array
    {
        $dependents = [];
        $entries = is_dir($modulesDir) ? (scandir($modulesDir) ?: []) : [];

        foreach ($entries as $entry) {
            if ($entry === '.' || $entry === '..' || $entry === $targetId) {
                continue;
            }
            $manifestFile = $modulesDir . '/' . $entry . '/module.php';
            if (!is_file($manifestFile)) {
                continue;
            }
            try {
                $manifest = ModuleManifestLoader::loadFromDisk($modulesDir, $entry);
                if (in_array($targetId, $manifest->requires, true)) {
                    $dependents[] = $entry;
                }
            } catch (\Throwable) {
                // Skip unparseable manifests
            }
        }

        sort($dependents);
        return $dependents;
    }
}
