<?php

declare(strict_types=1);

namespace Maestro\Core\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Maestro\Core\Support\PackagePaths;

class CoreInstallCommand extends Command
{
    protected $signature = 'core:install {--force : Overwrite managed stub files even if already installed}';

    protected $description = 'Publish Maestro CORE config, sync frontend stubs, and merge Composer / npm registry configuration';

    private const SATIS_URL = 'http://maestro-packages.build2connect.nl';

    private const VERDACCIO_SCOPE_REGISTRY = 'http://maestro-npm.build2connect.nl';

    /** Banner embedded in managed files — kept after edits so re-runs skip overwriting customizations. */
    private const MANAGED_BANNER = '@maestro-core-managed';

    /**
     * Vite config imports these from the app root; they are not always hoisted when @maestro/core is linked.
     *
     * @var array<string, string>
     */
    private const NPM_DEV_TOOLING = [
        '@primevue/auto-import-resolver' => '^4.3.1',
        '@vitejs/plugin-vue' => '^6.0.6',
        'sass-embedded' => '^1.93.3',
        'unplugin-vue-components' => '^0.27.5',
        'vue' => '^3.5.33',
    ];

    /** @var array<string, string> stub path under stubs/ => path relative to project root */
    private const MANAGED_STUBS = [
        'vite.config.js' => 'vite.config.js',
        'tailwind.config.js' => 'tailwind.config.js',
        'routes/web.php' => 'routes/web.php',
        'app/Models/User.php' => 'app/Models/User.php',
        'database/seeders/DatabaseSeeder.php' => 'database/seeders/DatabaseSeeder.php',
        'resources/css/app.css' => 'resources/css/app.css',
        'resources/js/app.js' => 'resources/js/app.js',
        'resources/js/pages/Dashboard.vue' => 'resources/js/pages/Dashboard.vue',
        'resources/views/spa.blade.php' => 'resources/views/spa.blade.php',
    ];

    public function handle(): int
    {
        $force = (bool) $this->option('force');

        $this->call('vendor:publish', [
            '--tag' => 'core-config',
            '--force' => $force,
        ]);

        $this->syncMaestroStubs($force);
        $this->mergeComposerRepositories();
        $this->ensureComposerRequire();
        $this->mergePackageJsonDependencies();
        $this->mergeNpmDevTooling();
        $this->ensureNpmrc();

        $this->newLine();
        $this->info('Maestro CORE scaffolding updated.');
        $this->newLine();
        $this->line('Next steps:');
        $this->line('  1. php artisan migrate');
        $this->line('  2. php artisan db:seed');
        $this->line('  3. npm install');
        $this->line('  4. npm run build (or composer run dev)');
        $this->newLine();
        $this->comment('Managed files contain '.self::MANAGED_BANNER.' — they are skipped on later installs unless you pass --force.');

        return self::SUCCESS;
    }

    /**
     * Copy stubs into the host Laravel app when missing or not yet managed by Maestro.
     */
    private function syncMaestroStubs(bool $force): void
    {
        foreach (self::MANAGED_STUBS as $stubRelative => $destinationRelative) {
            $source = $this->packageStubPath($stubRelative);
            $destination = base_path($destinationRelative);

            if (! File::exists($source)) {
                $this->warn('Missing package stub: '.$stubRelative);

                continue;
            }

            if (! $this->shouldWriteManagedFile($destination, $force)) {
                continue;
            }

            File::ensureDirectoryExists(dirname($destination));
            File::copy($source, $destination);
            $this->info('Synced '.$destinationRelative);
        }
    }

    private function shouldWriteManagedFile(string $destination, bool $force): bool
    {
        if ($force) {
            return true;
        }

        if (! File::exists($destination)) {
            return true;
        }

        return ! str_contains(File::get($destination), self::MANAGED_BANNER);
    }

    private function packageStubPath(string $relative): string
    {
        return PackagePaths::stubPath($relative);
    }

    private function mergeComposerRepositories(): void
    {
        $path = base_path('composer.json');
        if (! File::exists($path)) {
            return;
        }

        $data = json_decode(File::get($path), true);
        if (! is_array($data)) {
            return;
        }

        $repos = $data['repositories'] ?? [];
        $repos = is_array($repos) ? $repos : [];

        $exists = false;
        foreach ($repos as $repo) {
            if (($repo['url'] ?? '') === self::SATIS_URL) {
                $exists = true;
                break;
            }
        }

        if (! $exists) {
            $repos[] = ['type' => 'composer', 'url' => self::SATIS_URL];
            $data['repositories'] = $repos;
            File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
            $this->info('Added Satis repository to composer.json: '.self::SATIS_URL);
        }
    }

    private function ensureComposerRequire(): void
    {
        $path = base_path('composer.json');
        if (! File::exists($path)) {
            return;
        }

        $data = json_decode(File::get($path), true);
        if (! is_array($data)) {
            return;
        }

        $require = $data['require'] ?? [];
        $require = is_array($require) ? $require : [];

        if (! isset($require['maestro/core'])) {
            $require['maestro/core'] = '^1.0';
            $data['require'] = $require;
            File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
            $this->warn('Added maestro/core to composer.json require — run: composer update maestro/core');
        }
    }

    private function mergePackageJsonDependencies(): void
    {
        $path = base_path('package.json');
        if (! File::exists($path)) {
            return;
        }

        $data = json_decode(File::get($path), true);
        if (! is_array($data)) {
            return;
        }

        $deps = $data['dependencies'] ?? [];
        $deps = is_array($deps) ? $deps : [];

        if (! isset($deps['@maestro/core'])) {
            $deps['@maestro/core'] = '^1.0.6';
            ksort($deps);
            $data['dependencies'] = $deps;
            File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
            $this->warn('Added @maestro/core to package.json — run: npm install');
        }
    }

    /**
     * Ensure Vite / Vue tooling exists at the app root (required by vite.config.js stubs).
     */
    private function mergeNpmDevTooling(): void
    {
        $path = base_path('package.json');
        if (! File::exists($path)) {
            return;
        }

        $data = json_decode(File::get($path), true);
        if (! is_array($data)) {
            return;
        }

        $dev = $data['devDependencies'] ?? [];
        $dev = is_array($dev) ? $dev : [];

        $changed = false;
        foreach (self::NPM_DEV_TOOLING as $pkg => $version) {
            if (! isset($dev[$pkg])) {
                $dev[$pkg] = $version;
                $changed = true;
            }
        }

        if ($changed) {
            ksort($dev);
            $data['devDependencies'] = $dev;
            File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL);
            $this->warn('Added Maestro Vite/Vue devDependencies — run: npm install');
        }
    }

    private function ensureNpmrc(): void
    {
        $path = base_path('.npmrc');
        $line = '@maestro:registry='.self::VERDACCIO_SCOPE_REGISTRY.'/';

        if (File::exists($path)) {
            $contents = File::get($path);
            if (str_contains($contents, '@maestro:registry=')) {
                return;
            }
            File::append($path, PHP_EOL.$line.PHP_EOL);
            $this->info('Appended @maestro registry line to .npmrc');

            return;
        }

        File::put($path, $line.PHP_EOL);
        $this->info('Created .npmrc with Verdaccio scope for @maestro');
    }
}
