<?php

declare(strict_types=1);

namespace App\Services\Install;

use Closure;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Throwable;

/**
 * Stage 4 — 마이그레이션 + 시드 (basic|demo|full).
 *
 * GAP-06: seed_mode 분기
 *   - basic: roles + permissions + isms + tenant
 *   - demo: + boards + manuals + pages 샘플
 *   - full: + marketplace + shop + showcase + legal + patron
 */
final class Migrator
{
    /** @var list<array{key:string,label:string}> */
    private const STEPS = [
        ['key' => 'config_clear', 'label' => '구성 캐시 초기화'],
        ['key' => 'cache_clear',  'label' => '애플리케이션 캐시 초기화'],
        ['key' => 'migrate',      'label' => 'DB 마이그레이션 실행'],
        ['key' => 'seed_basic',   'label' => '기본 시드 (역할 / 권한 / ISMS)'],
        ['key' => 'seed_demo',    'label' => '데모 시드 (게시판 / 페이지 / 매뉴얼)'],
        ['key' => 'seed_full',    'label' => '풀 시드 (마켓 / 쇼핑몰 / 데모 사이트)'],
        ['key' => 'storage_link', 'label' => 'storage 심볼릭 링크 생성'],
        ['key' => 'optimize',     'label' => '구성 / 라우트 캐시 최적화'],
    ];

    public function newToken(string $seedMode = 'basic'): string
    {
        $token = (string) Str::ulid();
        $this->putProgress($token, [
            'state'   => 'pending',
            'step'    => 0,
            'total'   => count(self::STEPS),
            'current' => null,
            'log'     => [],
            'seed_mode' => $seedMode,
            'started_at' => now()->toIso8601String(),
        ]);

        return $token;
    }

    /**
     * @param  Closure(string,string):void|null  $onLog
     */
    public function run(string $token, string $seedMode = 'basic', ?Closure $onLog = null): bool
    {
        $log = [];
        $total = count(self::STEPS);

        foreach (self::STEPS as $i => $step) {
            // Skip demo/full seeds based on mode
            if ($step['key'] === 'seed_demo' && ! in_array($seedMode, ['demo', 'full'], true)) {
                $msg = "⤳ {$step['label']} (건너뜀 — seed_mode=basic)";
                $log[] = $msg;
                if ($onLog) $onLog('info', $msg);
                continue;
            }
            if ($step['key'] === 'seed_full' && $seedMode !== 'full') {
                $msg = "⤳ {$step['label']} (건너뜀 — seed_mode={$seedMode})";
                $log[] = $msg;
                if ($onLog) $onLog('info', $msg);
                continue;
            }

            $this->putProgress($token, [
                'state'   => 'running',
                'step'    => $i,
                'total'   => $total,
                'current' => $step['label'],
                'log'     => $log,
                'seed_mode' => $seedMode,
            ]);

            try {
                $this->dispatch($step['key']);
                $msg = "✓ {$step['label']}";
            } catch (Throwable $e) {
                $msg = "✗ {$step['label']} — ".$e->getMessage();
                $log[] = $msg;
                if ($onLog) $onLog('error', $msg);
                $this->putProgress($token, [
                    'state'   => 'failed',
                    'step'    => $i,
                    'total'   => $total,
                    'current' => $step['label'],
                    'error'   => $e->getMessage(),
                    'log'     => $log,
                    'seed_mode' => $seedMode,
                ]);

                return false;
            }

            $log[] = $msg;
            if ($onLog) $onLog('info', $msg);
        }

        $this->putProgress($token, [
            'state'    => 'done',
            'step'     => $total,
            'total'    => $total,
            'current'  => '완료',
            'log'      => $log,
            'seed_mode' => $seedMode,
            'finished_at' => now()->toIso8601String(),
        ]);

        return true;
    }

    private function dispatch(string $key): void
    {
        match ($key) {
            'config_clear' => Artisan::call('config:clear'),
            'cache_clear'  => Artisan::call('cache:clear'),
            'migrate'      => Artisan::call('migrate', ['--force' => true]),
            'seed_basic'   => $this->callSeederSafe(['DefaultRolesSeeder', 'DefaultPermissionsSeeder', 'DefaultIsmsControlsSeeder', 'DefaultSiteTemplateSeeder']),
            'seed_demo'    => $this->callSeederSafe(['DefaultBoardsSeeder', 'DefaultManualsSeeder', 'DefaultResourcesSeeder']),
            'seed_full'    => $this->callSeederSafe(['DefaultMarketplaceSeeder', 'DefaultShopSeeder', 'DefaultShowcaseSeeder', 'DefaultLegalDocumentsSeeder', 'DefaultPatronAdsSeeder']),
            'storage_link' => Artisan::call('storage:link'),
            'optimize'     => Artisan::call('optimize'),
            default        => throw new \LogicException("Unknown step: {$key}"),
        };
    }

    /**
     * 시더 클래스가 존재할 때만 실행 (다른 에이전트가 아직 만들지 않은 시더는 skip).
     */
    private function callSeederSafe(array $classes): void
    {
        foreach ($classes as $class) {
            $fqn = "Database\\Seeders\\{$class}";
            if (! class_exists($fqn)) {
                continue;
            }
            Artisan::call('db:seed', ['--class' => $fqn, '--force' => true]);
        }
    }

    public function progress(string $token): array
    {
        return Cache::store('file')->get($this->cacheKey($token)) ?? [
            'state' => 'unknown',
            'step'  => 0,
            'total' => count(self::STEPS),
        ];
    }

    private function putProgress(string $token, array $payload): void
    {
        Cache::store('file')->put($this->cacheKey($token), $payload, now()->addMinutes(30));
    }

    private function cacheKey(string $token): string
    {
        return "installer:progress:{$token}";
    }
}
