<?php

declare(strict_types=1);

class ImportProcessor
{
    private const DEFAULT_BATCH_SIZE = 3;
    private const MAX_BATCH_SIZE = 6;
    private const STALE_SECONDS = 300;  
    private const CRON_INTERVAL = 30;   
    private const MAX_TICK_SECONDS = 8;  

    public static function registerCron(): void
    {
        CronManager::register('import_processor', self::CRON_INTERVAL, [self::class, 'tick']);
    }

    public static function createJob(
        string $filename,
        string $originalName,
        array $mapping,
        array $options,
        int $totalRows,
        int $userId,
        int $startOffset = 0
    ): int {
        return Database::insert('import_jobs', [
            'filename'       => $filename,
            'original_name'  => $originalName,
            'mapping'        => json_encode($mapping),
            'options'        => json_encode($options),
            'status'         => 'pending',
            'total_rows'     => $totalRows,
            'processed_rows' => $startOffset,
            'file_offset'    => 0,
            'imported_count' => 0,
            'skipped_count'  => 0,
            'error_count'    => 0,
            'user_id'        => $userId,
        ]);
    }

    

    private static function getJobBatchSize(array $options): int
    {
        $speed = (int) ($options['importSpeed'] ?? self::DEFAULT_BATCH_SIZE);
        return max(1, min(self::MAX_BATCH_SIZE, $speed));
    }

    

    public static function tick(): void
    {
        self::recoverStalledJobs();

         
         
         
         
        $start = time();
        for ($i = 0; $i < 1000; $i++) {
            if ((time() - $start) >= self::MAX_TICK_SECONDS) {
                break;
            }
            $job = Database::fetchOne(
                "SELECT * FROM import_jobs WHERE status IN ('pending', 'processing') ORDER BY updated_at ASC, id ASC LIMIT 1"
            );
            if (!$job) {
                break;
            }
             
             
            if (!self::processBatch($job)) {
                break;
            }
        }
    }

    

    private static function processBatch(array $job): bool
    {
        $jobId = (int) $job['id'];

         
         
         
        $lockName = 'tp_import_job_' . $jobId;
        $lk = Database::fetchOne('SELECT GET_LOCK(?, 0) AS l', [$lockName]);
        if (!$lk || (int) $lk['l'] !== 1) {
            return false;
        }

        try {
        if (!self::acquireLock($jobId, $job['updated_at'])) {
            return false;
        }

         
         
        if ($job['status'] === 'pending') {
            Database::query(
                "UPDATE import_jobs SET status = 'processing', started_at = NOW() WHERE id = ? AND status = 'pending'",
                [$jobId]
            );
        }

        $filename = $job['filename'];
        $filePath = APP_ROOT . '/storage/imports/' . $filename;

        if (!file_exists($filePath)) {
            Database::query(
                "UPDATE import_jobs SET status = 'failed', error_message = 'Import file not found.', completed_at = NOW() WHERE id = ?",
                [$jobId]
            );
            return false;
        }

        $mapping    = json_decode($job['mapping'], true) ?: [];
        $options    = json_decode($job['options'], true) ?: [];
        $offset     = (int) $job['processed_rows'];
        $total      = (int) $job['total_rows'];
        $userId     = (int) $job['user_id'];
        $fileOffset = (int) ($job['file_offset'] ?? 0);

        $batchSize = self::getJobBatchSize($options);

        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        $newFileOffset = 0;

        try {
            if ($ext === 'csv') {
                if ($fileOffset > 0) {
                     
                    $result = AdminImportController::readCsvBatchFast($filePath, $fileOffset, $batchSize);
                } else {
                     
                    $result = AdminImportController::readCsvBatchFast($filePath, 0, $batchSize, $offset);
                }
                $batch = $result['rows'];
                $newFileOffset = $result['newOffset'];
            } else {
                $allRows = AdminImportController::readAllJson($filePath);
                $batch = array_slice($allRows, $offset, $batchSize);
            }
        } catch (\Throwable $e) {
            Database::query(
                "UPDATE import_jobs SET status = 'failed', error_message = ?, completed_at = NOW() WHERE id = ?",
                ['File read error: ' . $e->getMessage(), $jobId]
            );
            return false;
        }

        if (empty($batch)) {
             
             
             
            $finished = $offset >= $total;
            Database::query(
                "UPDATE import_jobs SET status = ?, error_message = ?, completed_at = NOW() WHERE id = ?",
                [$finished ? 'completed' : 'failed', $finished ? null : ('Unexpected end of file at row ' . $offset . ' of ' . $total . ' - the import file may have changed or been truncated.'), $jobId]
            );
            return false;
        }

        $imported = 0;
        $skipped  = 0;
        $errors   = 0;
        $errorSamples = [];

        foreach ($batch as $rowIndex => $row) {
            try {
                $result = AdminImportController::importRow($row, $mapping, $options, $userId);
                if (is_array($result)) {
                    if ($result['status'] === 'imported' || $result['status'] === 'updated') {
                        $imported++;
                    } else {
                        $skipped++;
                    }
                }
            } catch (\Throwable $e) {
                $errors++;
                if (count($errorSamples) < 5) {
                    $rowNum = $offset + $rowIndex + 1;
                    $rowTitle = trim((string) ($row[$mapping['title'] ?? ''] ?? ''));
                    $label = $rowTitle !== '' ? '"' . mb_substr($rowTitle, 0, 50) . '"' : "#{$rowNum}";
                    $errorSamples[] = "Row {$label}: " . mb_substr($e->getMessage(), 0, 200);
                }
            }
        }

        $newProcessed = $offset + count($batch);
        $done = $newProcessed >= $total;

         
        $errorMsg = null;
        if ($errorSamples) {
            $existing = $job['error_message'] ?? '';
            $newPart = implode("\n", $errorSamples);
            $errorMsg = $existing !== '' && $existing !== null
                ? $existing . "\n" . $newPart
                : $newPart;
            if (strlen($errorMsg) > 4000) {
                $errorMsg = '...' . substr($errorMsg, -3997);
            }
        }

         
        $newStatus = $done ? 'completed' : 'processing';
        $completedAt = $done ? date('Y-m-d H:i:s') : null;

        if ($errorMsg !== null) {
            Database::query(
                "UPDATE import_jobs SET
                    processed_rows = ?,
                    file_offset    = ?,
                    imported_count = imported_count + ?,
                    skipped_count  = skipped_count + ?,
                    error_count    = error_count + ?,
                    status         = ?,
                    completed_at   = ?,
                    error_message  = ?
                WHERE id = ? AND status IN ('pending', 'processing')",
                [$newProcessed, $newFileOffset, $imported, $skipped, $errors, $newStatus, $completedAt, $errorMsg, $jobId]
            );
        } else {
            Database::query(
                "UPDATE import_jobs SET
                    processed_rows = ?,
                    file_offset    = ?,
                    imported_count = imported_count + ?,
                    skipped_count  = skipped_count + ?,
                    error_count    = error_count + ?,
                    status         = ?,
                    completed_at   = ?
                WHERE id = ? AND status IN ('pending', 'processing')",
                [$newProcessed, $newFileOffset, $imported, $skipped, $errors, $newStatus, $completedAt, $jobId]
            );
        }

        return !$done;
        } finally {
            Database::query('SELECT RELEASE_LOCK(?)', [$lockName]);
        }
    }

    private static function acquireLock(int $jobId, string $currentUpdatedAt): bool
    {
        $affected = Database::query(
            "UPDATE import_jobs SET updated_at = NOW() WHERE id = ? AND updated_at = ?",
            [$jobId, $currentUpdatedAt]
        )->rowCount();

        return $affected > 0;
    }

    private static function recoverStalledJobs(): void
    {
        Database::query(
            "UPDATE import_jobs SET status = 'pending'
             WHERE status = 'processing'
               AND updated_at < DATE_SUB(NOW(), INTERVAL ? SECOND)",
            [self::STALE_SECONDS]
        );
    }

    public static function getActiveJobs(): array
    {
        return Database::fetchAll(
            "SELECT * FROM import_jobs
             WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
             ORDER BY
                CASE status
                    WHEN 'processing' THEN 1
                    WHEN 'pending'    THEN 2
                    WHEN 'paused'     THEN 3
                    WHEN 'completed'  THEN 4
                    WHEN 'failed'     THEN 5
                    WHEN 'cancelled'  THEN 6
                END,
                id DESC"
        );
    }

    public static function getJob(int $id): ?array
    {
        return Database::fetchOne("SELECT * FROM import_jobs WHERE id = ?", [$id]);
    }

    public static function cancelJob(int $id): bool
    {
        $affected = Database::query(
            "UPDATE import_jobs SET status = 'cancelled', completed_at = NOW()
             WHERE id = ? AND status IN ('pending', 'processing', 'paused')",
            [$id]
        )->rowCount();

        return $affected > 0;
    }

    public static function pauseJob(int $id): bool
    {
        $affected = Database::query(
            "UPDATE import_jobs SET status = 'paused'
             WHERE id = ? AND status IN ('pending', 'processing')",
            [$id]
        )->rowCount();

        return $affected > 0;
    }

    public static function resumeJob(int $id): bool
    {
        $affected = Database::query(
            "UPDATE import_jobs SET status = 'pending'
             WHERE id = ? AND status = 'paused'",
            [$id]
        )->rowCount();

        return $affected > 0;
    }

    public static function deleteJob(int $id): bool
    {
         
        $affected = Database::query(
            "DELETE FROM import_jobs
             WHERE id = ? AND status IN ('completed', 'cancelled', 'failed', 'paused')",
            [$id]
        )->rowCount();

        return $affected > 0;
    }
}
