<?php

declare(strict_types=1);
final class Asm3ImportController
{
    public static function index(): void
    {
        self::admin();
        $jobs = DB::pdo()
            ->query(
                'SELECT j.*,COALESCE(u.display_name,u.username) actor FROM asm3_import_jobs j LEFT JOIN users u ON u.id=j.created_by ORDER BY j.id DESC LIMIT 20',
            )
            ->fetchAll(PDO::FETCH_ASSOC);
        render('settings_asm3.php', [
            'title' => t('asm3.title'),
            'jobs' => $jobs,
            'error' => (string) ($_GET['error'] ?? ''),
            'success' => (string) ($_GET['success'] ?? ''),
        ]);
    }
    public static function upload(): void
    {
        self::admin();
        $f = $_FILES['dump'] ?? null;
        if (!is_array($f)) {
            self::fail(
                self::postTooLarge()
                    ? t('asm3.post_too_large', ['limit' => ini_get('post_max_size')])
                    : t('asm3.select_dump'),
            );
        }
        $uploadError = (int) ($f['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($uploadError !== UPLOAD_ERR_OK) {
            self::fail(self::uploadErrorMessage($uploadError));
        }
        if (!is_uploaded_file((string) $f['tmp_name'])) {
            self::fail(t('asm3.upload_unverified'));
        }
        if ((int) $f['size'] > 250_000_000) {
            self::fail(t('asm3.over_250'));
        }
        $name = (string) $f['name'];
        $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        if (!in_array($extension, ['sql', 'pgsql'], true)) {
            self::fail(t('asm3.extension'));
        }
        $dir = dirname(__DIR__, 2) . '/storage/asm3-imports';
        if (!is_dir($dir) && !mkdir($dir, 0700, true) && !is_dir($dir)) {
            self::fail(t('asm3.storage_unavailable'));
        }
        $stored = bin2hex(random_bytes(16)) . '.' . $extension;
        $path = $dir . '/' . $stored;
        if (!move_uploaded_file((string) $f['tmp_name'], $path)) {
            self::fail(t('asm3.storage_failed'));
        }
        chmod($path, 0600);
        try {
            $analysis = Asm3Analyzer::analyze($path);
            if (($analysis['tables']['animal'] ?? 0) < 1) {
                throw new RuntimeException(t('asm3.no_animal'));
            }
            $animals = Asm3MigrationPlanner::plan($path, DB::pdo());
            $contacts = Asm3ContactPlanner::plan($path, DB::pdo());
            $report = [
                'analysis' => $analysis,
                'animals' => $animals['summary'],
                'contacts' => $contacts['summary'],
                'movements' => Asm3MovementImporter::run($path, DB::pdo(), false)['summary'],
                'clinical' => Asm3ClinicalImporter::run($path, DB::pdo(), false),
                'litters' => Asm3LitterImporter::run($path, DB::pdo(), false),
                'media' => [
                    'metadata' => $analysis['tables']['media'] ?? 0,
                    'importable' => 0,
                    'reason' => 'Le dump SQL ne contient pas les fichiers binaires ASM3.',
                ],
            ];
            $db = DB::pdo();
            $db->prepare(
                'INSERT INTO asm3_import_jobs(original_name,stored_name,source_sha256,analysis_json,created_by) VALUES(:original,:stored,:hash,:analysis,:user)',
            )->execute([
                ':original' => basename($name),
                ':stored' => $stored,
                ':hash' => hash_file('sha256', $path),
                ':analysis' => json_encode($report, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
                ':user' => Auth::id(),
            ]);
            AuditService::log('asm3_analyzed', '/settings/asm3', 'Dump ASM3 analysé : ' . basename($name));
            header('Location: /settings/asm3?success=analyzed');
            exit();
        } catch (Throwable $e) {
            @unlink($path);
            self::fail($e->getMessage());
        }
    }
    public static function apply(): void
    {
        self::admin();
        $id = (int) ($_POST['id'] ?? 0);
        $mode = ($_POST['mode'] ?? 'merge') === 'replace' ? 'replace' : 'merge';
        $expected = t($mode === 'replace' ? 'asm3.confirm_replace_phrase' : 'asm3.confirm_import_phrase');
        if (trim((string) ($_POST['confirmation'] ?? '')) !== $expected) {
            self::fail(t('asm3.confirm_required', ['phrase' => $expected]));
        }
        $db = DB::pdo();
        $q = $db->prepare("SELECT * FROM asm3_import_jobs WHERE id=:id AND status='analyzed'");
        $q->execute([':id' => $id]);
        $job = $q->fetch(PDO::FETCH_ASSOC);
        $q->closeCursor();
        unset($q);
        if (!$job) {
            self::fail(t('asm3.analysis_unavailable'));
        }
        $path = dirname(__DIR__, 2) . '/storage/asm3-imports/' . basename($job['stored_name']);
        if (!is_file($path) || !hash_equals($job['source_sha256'], hash_file('sha256', $path))) {
            self::fail(t('asm3.dump_altered'));
        }
        try {
            $backup = BackupService::create('pre-asm3-import');
            $db->beginTransaction();
            $removed = $mode === 'replace' ? Asm3ResetService::clear($db) : null;
            $results = [
                'mode' => $mode,
                'backup' => $backup['name'],
                'removed' => $removed,
                'animals' => Asm3AnimalImporter::run($path, $db, true, false),
                'contacts' => Asm3ContactImporter::run($path, $db, true, false),
                'movements' => Asm3MovementImporter::run($path, $db, true, false),
                'clinical' => Asm3ClinicalImporter::run($path, $db, true, false),
                'litters' => Asm3LitterImporter::run($path, $db, true, false),
            ];
            $db->prepare(
                "UPDATE asm3_import_jobs SET status='completed',result_json=:result,completed_at=datetime('now') WHERE id=:id",
            )->execute([
                ':result' => json_encode($results, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
                ':id' => $id,
            ]);
            $db->commit();
            if ($mode === 'replace') {
                Asm3ResetService::clearMedia();
            }
            AuditService::log(
                'asm3_imported',
                '/settings/asm3/apply',
                'Migration ASM3 terminée en mode ' . $mode . ' depuis ' . $job['original_name'],
            );
            header('Location: /settings/asm3?success=imported');
            exit();
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            $db->prepare(
                "UPDATE asm3_import_jobs SET status='failed',result_json=:result,completed_at=datetime('now') WHERE id=:id",
            )->execute([
                ':result' => json_encode(['error' => $e->getMessage(), 'mode' => $mode], JSON_UNESCAPED_UNICODE),
                ':id' => $id,
            ]);
            self::fail(t('asm3.import_failed', ['error' => $e->getMessage()]));
        }
    }
    private static function uploadErrorMessage(int $error): string
    {
        return match ($error) {
            UPLOAD_ERR_INI_SIZE => t('asm3.upload_ini', ['limit' => ini_get('upload_max_filesize')]),
            UPLOAD_ERR_FORM_SIZE => t('asm3.upload_form'),
            UPLOAD_ERR_PARTIAL => t('asm3.upload_partial'),
            UPLOAD_ERR_NO_FILE => t('asm3.select_dump'),
            UPLOAD_ERR_NO_TMP_DIR => t('asm3.tmp_unavailable'),
            UPLOAD_ERR_CANT_WRITE => t('asm3.write_failed'),
            UPLOAD_ERR_EXTENSION => t('asm3.extension_stopped'),
            default => t('asm3.upload_failed', ['code' => $error]),
        };
    }
    private static function postTooLarge(): bool
    {
        $length = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
        $limit = self::iniBytes((string) ini_get('post_max_size'));
        return $length > 0 && $limit > 0 && $length > $limit;
    }
    private static function iniBytes(string $value): int
    {
        $value = trim($value);
        if ($value === '') {
            return 0;
        }
        $number = (float) $value;
        return (int) round(
            $number *
                match (strtolower(substr($value, -1))) {
                    'g' => 1024 ** 3,
                    'm' => 1024 ** 2,
                    'k' => 1024,
                    default => 1,
                },
        );
    }
    private static function fail(string $message): never
    {
        header('Location: /settings/asm3?error=' . rawurlencode($message));
        exit();
    }
    private static function admin(): void
    {
        if (!Auth::is('admin')) {
            http_response_code(403);
            exit();
        }
    }
}
