<?php

function adminJobStatusLabels() {
    return [
        'queued' => '대기',
        'running' => '진행 중',
        'completed' => '완료',
        'failed' => '실패',
        'canceled' => '취소',
    ];
}

function adminJobTypeLabels() {
    return [
        'scan' => '음악 스캔',
        'metadata' => '메타데이터 정리',
        'backup' => '백업',
        'export' => '내보내기',
        'import' => '가져오기',
        'restore' => '복원',
        'maintenance' => '점검 작업',
    ];
}

function adminJobStatusLabel($status) {
    $labels = adminJobStatusLabels();
    return $labels[$status] ?? '알 수 없음';
}

function adminJobTypeLabel($type) {
    $labels = adminJobTypeLabels();
    return $labels[$type] ?? '관리자 작업';
}

function adminJobStatusClass($status) {
    if ($status === 'completed') return 'ok';
    if ($status === 'failed') return 'danger';
    if ($status === 'running') return 'info';
    if ($status === 'canceled') return 'muted';
    return 'warning';
}

function adminJobCleanToken($value, $fallback = 'job') {
    $value = strtolower(trim((string)$value));
    $value = preg_replace('/[^a-z0-9_\-:]/', '_', $value);
    $value = trim($value, '_:');
    return $value !== '' ? substr($value, 0, 120) : $fallback;
}

function adminJobSafeText($value, $limit = 500) {
    $value = ensureUtf8String($value);
    $value = maskInternalPathText($value);
    $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/u', ' ', $value);
    $value = trim((string)$value);
    return limitLogText($value, $limit);
}

function adminJobSafeContext($value) {
    if (is_array($value)) {
        $result = [];
        foreach ($value as $key => $item) {
            $safeKey = adminJobSafeText((string)$key, 80);
            if ($safeKey === '') continue;
            $result[$safeKey] = adminJobSafeContext($item);
        }
        return $result;
    }
    if (is_bool($value) || is_int($value) || is_float($value) || $value === null) {
        return $value;
    }
    return adminJobSafeText((string)$value, 300);
}

function adminJobEncodeContext($context) {
    $context = adminJobSafeContext($context);
    if (empty($context)) return null;
    $json = json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
    return $json !== false ? $json : null;
}

function ensureAdminJobsTable() {
    $pdo = getDBConnection();
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS `admin_jobs` (
            `id` INT AUTO_INCREMENT PRIMARY KEY,
            `job_key` VARCHAR(120) NOT NULL,
            `job_type` VARCHAR(40) NOT NULL,
            `job_name` VARCHAR(160) NOT NULL,
            `status` VARCHAR(20) NOT NULL DEFAULT 'queued',
            `progress` TINYINT UNSIGNED NOT NULL DEFAULT 0,
            `processed_count` INT NOT NULL DEFAULT 0,
            `failed_count` INT NOT NULL DEFAULT 0,
            `requested_by_admin` INT DEFAULT NULL,
            `summary_message` TEXT DEFAULT NULL,
            `error_type` VARCHAR(120) DEFAULT NULL,
            `context` TEXT DEFAULT NULL,
            `started_at` DATETIME DEFAULT NULL,
            `finished_at` DATETIME DEFAULT NULL,
            `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
            `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX `idx_status_updated` (`status`, `updated_at`),
            INDEX `idx_type_status` (`job_type`, `status`),
            INDEX `idx_created_at` (`created_at`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
    ");
}

function getActiveAdminJob($type, $jobKey = null, $staleMinutes = 180) {
    try {
        ensureAdminJobsTable();
        $type = adminJobCleanToken($type, 'job');
        $staleMinutes = max(5, min(1440, (int)$staleMinutes));
        $where = [
            "job_type = :job_type",
            "status IN ('queued', 'running')",
            "updated_at >= DATE_SUB(NOW(), INTERVAL {$staleMinutes} MINUTE)",
        ];
        $params = [':job_type' => $type];
        if ($jobKey !== null && $jobKey !== '') {
            $where[] = 'job_key = :job_key';
            $params[':job_key'] = adminJobCleanToken($jobKey, $type);
        }
        $stmt = getDBConnection()->prepare("
            SELECT id, job_key, job_type, job_name, status, progress, processed_count, failed_count, started_at, updated_at
            FROM admin_jobs
            WHERE " . implode(' AND ', $where) . "
            ORDER BY id DESC
            LIMIT 1
        ");
        $stmt->execute($params);
        return $stmt->fetch() ?: null;
    } catch (Throwable $e) {
        return null;
    }
}

function startAdminJob($type, $name, $jobKey = null, $context = [], $preventDuplicate = true) {
    try {
        ensureAdminJobsTable();
        $type = adminJobCleanToken($type, 'job');
        $name = adminJobSafeText($name, 160);
        if ($name === '') $name = adminJobTypeLabel($type);
        $jobKey = adminJobCleanToken($jobKey ?: ($type . ':' . substr(sha1($name . json_encode(adminJobSafeContext($context))), 0, 16)), $type);

        if ($preventDuplicate) {
            $active = getActiveAdminJob($type, $jobKey);
            if ($active) {
                return ['ok' => false, 'duplicate' => true, 'job' => $active, 'id' => (int)$active['id']];
            }
        }

        $stmt = getDBConnection()->prepare("
            INSERT INTO admin_jobs
                (job_key, job_type, job_name, status, progress, requested_by_admin, summary_message, context, started_at)
            VALUES
                (:job_key, :job_type, :job_name, 'running', :progress, :requested_by_admin, :summary_message, :context, NOW())
        ");
        $stmt->execute([
            ':job_key' => $jobKey,
            ':job_type' => $type,
            ':job_name' => $name,
            ':progress' => 5,
            ':requested_by_admin' => $_SESSION['admin_user_id'] ?? null,
            ':summary_message' => '작업을 시작했습니다.',
            ':context' => adminJobEncodeContext($context),
        ]);
        $id = (int)getDBConnection()->lastInsertId();
        if (function_exists('writeAppLog')) {
            writeAppLog('info', 'admin_job', 'Admin job started', ['job_id' => $id, 'job_type' => $type, 'job_key' => $jobKey]);
        }
        return ['ok' => true, 'duplicate' => false, 'id' => $id];
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('warning', 'admin_job_error', 'Admin job start tracking failed', ['error' => $e->getMessage()]);
        }
        return ['ok' => true, 'duplicate' => false, 'id' => null];
    }
}

function finishAdminJob($jobId, $status, $data = []) {
    $jobId = (int)$jobId;
    if ($jobId <= 0) return false;
    $allowed = ['running', 'completed', 'failed', 'canceled'];
    $status = in_array($status, $allowed, true) ? $status : 'completed';
    $isFinal = in_array($status, ['completed', 'failed', 'canceled'], true);
    $progress = isset($data['progress']) ? (int)$data['progress'] : ($isFinal ? 100 : 50);
    $progress = max(0, min(100, $progress));
    $summary = adminJobSafeText($data['summary_message'] ?? $data['message'] ?? '', 900);
    $errorType = adminJobSafeText($data['error_type'] ?? '', 120);

    try {
        ensureAdminJobsTable();
        $stmt = getDBConnection()->prepare("
            UPDATE admin_jobs
            SET status = :status,
                progress = :progress,
                processed_count = :processed_count,
                failed_count = :failed_count,
                summary_message = :summary_message,
                error_type = :error_type,
                context = COALESCE(:context, context),
                finished_at = CASE WHEN :is_final = 1 THEN NOW() ELSE finished_at END,
                updated_at = NOW()
            WHERE id = :id
        ");
        $stmt->execute([
            ':status' => $status,
            ':progress' => $progress,
            ':processed_count' => max(0, (int)($data['processed_count'] ?? 0)),
            ':failed_count' => max(0, (int)($data['failed_count'] ?? 0)),
            ':summary_message' => $summary !== '' ? $summary : adminJobStatusLabel($status),
            ':error_type' => $errorType !== '' ? $errorType : null,
            ':context' => array_key_exists('context', $data) ? adminJobEncodeContext($data['context']) : null,
            ':is_final' => $isFinal ? 1 : 0,
            ':id' => $jobId,
        ]);
        if ($status === 'failed') {
            recordAdminJobFailureNotification($jobId, $summary, $errorType);
        }
        if (function_exists('writeAppLog')) {
            writeAppLog($status === 'failed' ? 'warning' : 'info', 'admin_job', 'Admin job ' . $status, [
                'job_id' => $jobId,
                'status' => $status,
                'error_type' => $errorType,
            ]);
        }
        return true;
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('warning', 'admin_job_error', 'Admin job finish tracking failed', ['job_id' => $jobId, 'error' => $e->getMessage()]);
        }
        return false;
    }
}

function recordAdminJobFailureNotification($jobId, $summary, $errorType = '') {
    if (!function_exists('upsertAdminNotification')) {
        $notificationHelper = __DIR__ . '/admin_notifications.php';
        if (is_file($notificationHelper)) {
            require_once $notificationHelper;
        }
    }
    if (!function_exists('upsertAdminNotification')) return;
    try {
        upsertAdminNotification([
            'category' => 'operation',
            'level' => 'warning',
            'status' => 'active',
            'title' => '관리자 작업 실패 확인 필요',
            'message' => $summary !== '' ? $summary : '관리자 작업이 실패했습니다.',
            'detail' => $errorType !== '' ? '오류 유형: ' . $errorType : '작업 큐에서 상세 상태를 확인하세요.',
            'related_type' => 'admin_job',
            'related_id' => (int)$jobId,
            'related_url' => 'jobs.php?status=failed',
        ]);
    } catch (Throwable $ignored) {
    }
}

function getAdminJobs($filters = []) {
    try {
        ensureAdminJobsTable();
        $page = max(1, (int)($filters['page'] ?? 1));
        $limit = max(10, min(80, (int)($filters['limit'] ?? 20)));
        $where = [];
        $params = [];
        $statuses = array_keys(adminJobStatusLabels());
        $types = array_keys(adminJobTypeLabels());

        if (!empty($filters['status']) && in_array($filters['status'], $statuses, true)) {
            $where[] = 'j.status = :status';
            $params[':status'] = $filters['status'];
        }
        if (!empty($filters['type']) && in_array($filters['type'], $types, true)) {
            $where[] = 'j.job_type = :job_type';
            $params[':job_type'] = $filters['type'];
        }
        $whereSql = $where ? ' WHERE ' . implode(' AND ', $where) : '';

        $countStmt = getDBConnection()->prepare("SELECT COUNT(*) FROM admin_jobs j" . $whereSql);
        $countStmt->execute($params);
        $total = (int)$countStmt->fetchColumn();
        $pages = max(1, (int)ceil($total / $limit));
        $page = min($page, $pages);
        $offset = ($page - 1) * $limit;

        $stmt = getDBConnection()->prepare("
            SELECT j.id, j.job_key, j.job_type, j.job_name, j.status, j.progress,
                   j.processed_count, j.failed_count, j.summary_message, j.error_type,
                   j.started_at, j.finished_at, j.created_at, j.updated_at,
                   au.username AS requested_username, au.display_name AS requested_display_name
            FROM admin_jobs j
            LEFT JOIN admin_users au ON au.id = j.requested_by_admin
            $whereSql
            ORDER BY j.id DESC
            LIMIT {$limit} OFFSET {$offset}
        ");
        $stmt->execute($params);
        return ['items' => $stmt->fetchAll(), 'total' => $total, 'page' => $page, 'pages' => $pages, 'limit' => $limit];
    } catch (Throwable $e) {
        return ['items' => [], 'total' => 0, 'page' => 1, 'pages' => 1, 'limit' => 20];
    }
}

function getAdminJobDashboardSummary() {
    try {
        ensureAdminJobsTable();
        $row = getDBConnection()->query("
            SELECT
                SUM(CASE WHEN status IN ('queued','running') THEN 1 ELSE 0 END) AS active_count,
                SUM(CASE WHEN status = 'failed' AND updated_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN 1 ELSE 0 END) AS failed_24h,
                SUM(CASE WHEN status = 'completed' AND updated_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN 1 ELSE 0 END) AS completed_24h,
                COUNT(*) AS total_count
            FROM admin_jobs
        ")->fetch() ?: [];
        return [
            'active' => (int)($row['active_count'] ?? 0),
            'failed_24h' => (int)($row['failed_24h'] ?? 0),
            'completed_24h' => (int)($row['completed_24h'] ?? 0),
            'total' => (int)($row['total_count'] ?? 0),
        ];
    } catch (Throwable $e) {
        return ['active' => 0, 'failed_24h' => 0, 'completed_24h' => 0, 'total' => 0];
    }
}
