<?php

namespace MintyPHP\Service\Database;

use MintyPHP\Repository\Database\CoreMigrationRepositoryInterface;
use MintyPHP\Support\SqlStatementParser;
use RuntimeException;

/**
 * Discovers and applies pending SQL migrations from db/updates/*.sql.
 *
 * Mirror of ModuleMigrationService for core schema changes. Each file MUST be
 * idempotent (CLAUDE.md / GR-CORE-010); applied filenames are tracked in
 * core_migrations so subsequent runs only apply genuinely new files.
 *
 * Orchestration only — SQL lives in CoreMigrationRepository,
 * statement parsing in SqlStatementParser.
 */
final class CoreMigrationService
{
    public function __construct(
        private readonly CoreMigrationRepositoryInterface $repository,
        private readonly string $updatesPath,
    ) {
    }

    /**
     * Apply all pending core migrations.
     *
     * @return array{ok: bool, applied: int, skipped_empty: int, total_files: int, applied_filenames: list<string>}
     */
    public function applyPendingMigrations(): array
    {
        $this->repository->ensureTrackingTable();

        $files = $this->discoverFiles();
        $applied = $this->repository->getAppliedFilenames();

        $totalApplied = 0;
        $skippedEmpty = 0;
        $appliedFilenames = [];

        foreach ($files as $file) {
            $filename = basename($file);
            if (isset($applied[$filename])) {
                continue;
            }

            $sql = file_get_contents($file);
            if ($sql === false || trim($sql) === '') {
                $skippedEmpty++;
                continue;
            }

            $this->repository->beginTransaction();
            try {
                $statements = SqlStatementParser::splitStatements($sql);
                foreach ($statements as $statement) {
                    $this->repository->executeStatement($statement);
                }

                $this->repository->recordApplied($filename);
                $this->repository->commit();
            } catch (\Throwable $migrationError) {
                $this->repository->rollback();
                throw new RuntimeException(
                    sprintf("Core migration %s failed: %s", $filename, $migrationError->getMessage()),
                    0,
                    $migrationError
                );
            }

            $totalApplied++;
            $appliedFilenames[] = $filename;
        }

        return [
            'ok' => true,
            'applied' => $totalApplied,
            'skipped_empty' => $skippedEmpty,
            'total_files' => count($files),
            'applied_filenames' => $appliedFilenames,
        ];
    }

    /**
     * Inspect applied vs. pending migrations without running anything.
     *
     * @return array{applied: list<string>, pending: list<string>}
     */
    public function status(): array
    {
        $this->repository->ensureTrackingTable();

        $files = $this->discoverFiles();
        $appliedMap = $this->repository->getAppliedFilenames();

        $applied = [];
        $pending = [];
        foreach ($files as $file) {
            $filename = basename($file);
            if (isset($appliedMap[$filename])) {
                $applied[] = $filename;
            } else {
                $pending[] = $filename;
            }
        }

        sort($applied);
        sort($pending);

        return ['applied' => $applied, 'pending' => $pending];
    }

    /**
     * @return list<string> Absolute paths of `*.sql` files in updates dir, sorted alphabetically.
     */
    private function discoverFiles(): array
    {
        if (!is_dir($this->updatesPath)) {
            return [];
        }
        $files = glob($this->updatesPath . '/*.sql');
        if ($files === false || count($files) === 0) {
            return [];
        }
        sort($files);
        return $files;
    }
}
