<?php
$isCli = (PHP_SAPI === 'cli');

require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/includes/auth.php';
if (!$isCli) {
    requireAdmin();
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code(405);
        echo json_encode(['success' => false, 'message' => '스캔은 POST 방식으로만 실행됩니다.'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    requireCsrfToken();
}
if (!$isCli) {
    requireAdmin();
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code(405);
        echo json_encode(['success' => false, 'message' => '스캔은 POST 방식으로만 실행됩니다.'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    requireCsrfToken();
}

set_time_limit(0);
ini_set('display_errors', 0);

$scanStartedAt = date('Y-m-d H:i:s');
$startTime = microtime(true);
$totalFound = 0;
$newCount = 0;
$updatedCount = 0;
$deletedCount = 0;
$skippedCount = 0;
$errorCount = 0;
$tagCount = 0;
$filenameCount = 0;
$manualProtectedCount = 0;
$coverSuccessCount = 0;
$coverMissingCount = 0;
$metadataErrorCount = 0;
$message = '';

function scanLogMessage($msg, $level = 'INFO') {
    global $isCli;
    writeScanLog($msg, $level);
    if (function_exists('writeAppLog') && in_array(strtoupper((string)$level), ['WARNING', 'ERROR'], true)) {
        writeAppLog(strtoupper((string)$level) === 'ERROR' ? 'error' : 'warning', 'scan_error', $msg, ['level' => $level], __FILE__);
    }
    if ($isCli) echo sprintf("[%s] [%s] %s%s", date('H:i:s'), $level, $msg, PHP_EOL);
}

function scanMetadataParams($metadata, $prefix = '') {
    return [
        $prefix . ':title' => $metadata['title'],
        $prefix . ':artist' => $metadata['artist'],
        $prefix . ':album' => $metadata['album'],
        $prefix . ':album_artist' => $metadata['album_artist'],
        $prefix . ':genre' => $metadata['genre'],
        $prefix . ':year' => $metadata['year'],
        $prefix . ':track_no' => $metadata['track_no'],
        $prefix . ':disc_no' => $metadata['disc_no'],
        $prefix . ':duration' => $metadata['duration'],
        $prefix . ':composer' => $metadata['composer'],
        $prefix . ':comment' => $metadata['comment'],
        $prefix . ':bitrate' => $metadata['bitrate'],
        $prefix . ':sample_rate' => $metadata['sample_rate'],
        $prefix . ':channels' => $metadata['channels'],
        $prefix . ':cover_hash' => $metadata['cover_hash'],
        $prefix . ':cover_mime' => $metadata['cover_mime'],
        $prefix . ':has_cover' => $metadata['has_cover'],
        $prefix . ':metadata_source' => $metadata['metadata_source'],
        $prefix . ':metadata_error' => $metadata['metadata_error'],
    ];
}

try {
    $pdo = getDBConnection();
    $scanPath = getScanPathSetting();
    $allowedExtensions = getAllowedExtensionsSetting();
    $refreshMetadata = settingBool('scan_refresh_metadata', false);
    $forceMetadata = settingBool('scan_force_metadata', false);
    $winScanPath = toWinPath($scanPath);

    scanLogMessage('Music scan started. Path: ' . $scanPath);

    if (!is_dir($winScanPath)) {
        throw new Exception('Scan path not found: ' . $scanPath);
    }

    $foundHashes = [];
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($winScanPath, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    $selectStmt = $pdo->prepare("
        SELECT id, file_size, file_mtime, is_missing, year, cover_hash, is_manual_edited
        FROM songs
        WHERE file_hash = :file_hash
    ");
    $insertStmt = $pdo->prepare("
        INSERT INTO songs (
            title, artist, album, album_artist, genre, year, track_no, disc_no, duration,
            composer, comment, bitrate, sample_rate, channels, file_path, file_hash,
            folder_path, folder_name, relative_path, file_size, file_mtime, extension, cover_hash, has_cover, cover_mime, metadata_source,
            metadata_updated_at, last_metadata_scan_at, metadata_error, is_manual_edited, is_missing
        ) VALUES (
            :title, :artist, :album, :album_artist, :genre, :year, :track_no, :disc_no, :duration,
            :composer, :comment, :bitrate, :sample_rate, :channels, :file_path, :file_hash,
            :folder_path, :folder_name, :relative_path, :file_size, :file_mtime, :extension, :cover_hash, :has_cover, :cover_mime, :metadata_source,
            NOW(), NOW(), :metadata_error, 0, 0
        )
    ");
    $updateFullStmt = $pdo->prepare("
        UPDATE songs
        SET title = :title,
            artist = :artist,
            album = :album,
            album_artist = :album_artist,
            genre = :genre,
            year = :year,
            track_no = :track_no,
            disc_no = :disc_no,
            duration = :duration,
            composer = :composer,
            comment = :comment,
            bitrate = :bitrate,
            sample_rate = :sample_rate,
            channels = :channels,
            file_path = :file_path,
            folder_path = :folder_path,
            folder_name = :folder_name,
            relative_path = :relative_path,
            file_size = :file_size,
            file_mtime = :file_mtime,
            extension = :extension,
            cover_hash = :cover_hash,
            has_cover = :has_cover,
            cover_mime = :cover_mime,
            cover_cache_path = NULL,
            metadata_source = :metadata_source,
            metadata_updated_at = NOW(),
            last_metadata_scan_at = NOW(),
            metadata_error = :metadata_error,
            is_missing = 0
        WHERE file_hash = :file_hash
    ");
    $updateTechnicalStmt = $pdo->prepare("
        UPDATE songs
        SET duration = :duration,
            bitrate = :bitrate,
            sample_rate = :sample_rate,
            channels = :channels,
            file_path = :file_path,
            folder_path = :folder_path,
            folder_name = :folder_name,
            relative_path = :relative_path,
            file_size = :file_size,
            file_mtime = :file_mtime,
            extension = :extension,
            cover_hash = :cover_hash,
            has_cover = :has_cover,
            cover_mime = :cover_mime,
            cover_cache_path = NULL,
            metadata_source = CASE WHEN metadata_source IS NULL OR metadata_source = '' THEN :metadata_source ELSE metadata_source END,
            last_metadata_scan_at = NOW(),
            metadata_updated_at = NOW(),
            metadata_error = :metadata_error,
            is_missing = 0
        WHERE file_hash = :file_hash
    ");
    $markActiveStmt = $pdo->prepare("UPDATE songs SET is_missing = 0 WHERE file_hash = :file_hash");

    foreach ($iterator as $fileInfo) {
        if (!$fileInfo->isFile()) continue;
        $winFilePath = $fileInfo->getPathname();
        $ext = strtolower(pathinfo($winFilePath, PATHINFO_EXTENSION));
        if (!in_array($ext, $allowedExtensions, true)) continue;

        $totalFound++;
        try {
            $utf8FilePath = toUtf8Path($winFilePath);
            $realWinPath = realpath($winFilePath) ?: $winFilePath;
            $fileHash = hash('sha256', toUtf8Path($realWinPath));
            $foundHashes[] = $fileHash;
            $fileSize = filesize($winFilePath);
            $fileMtime = filemtime($winFilePath);
            $metadata = extractMusicMetadata($utf8FilePath);
            $normalizedScanPath = rtrim(str_replace('\\', '/', $scanPath), '/') . '/';
            $normalizedFilePath = str_replace('\\', '/', $utf8FilePath);
            $relativePath = strpos($normalizedFilePath, $normalizedScanPath) === 0
                ? substr($normalizedFilePath, strlen($normalizedScanPath))
                : basename($normalizedFilePath);
            $folderPath = str_replace('\\', '/', dirname($utf8FilePath));
            $folderName = basename($folderPath);
            if (($metadata['tag_used'] ?? 0) == 1) $tagCount++;
            if (($metadata['filename_fallback_used'] ?? 0) == 1) $filenameCount++;
            if (!empty($metadata['has_cover'])) $coverSuccessCount++; else $coverMissingCount++;
            if (!empty($metadata['metadata_error'])) $metadataErrorCount++;

            $selectStmt->execute([':file_hash' => $fileHash]);
            $existingSong = $selectStmt->fetch();

            $fileParams = [
                ':file_path' => $utf8FilePath,
                ':file_hash' => $fileHash,
                ':folder_path' => $folderPath,
                ':folder_name' => $folderName,
                ':relative_path' => $relativePath,
                ':file_size' => $fileSize,
                ':file_mtime' => $fileMtime,
                ':extension' => $ext,
            ];

            if ($existingSong) {
                $shouldUpdate = $refreshMetadata
                    || $forceMetadata
                    || $existingSong['file_size'] != $fileSize
                    || $existingSong['file_mtime'] != $fileMtime
                    || $existingSong['is_missing'] == 1
                    || ($metadata['year'] !== null && $existingSong['year'] === null)
                    || ($metadata['cover_hash'] !== null && $existingSong['cover_hash'] === null);

                if ($shouldUpdate) {
                    $manualProtected = ((int)$existingSong['is_manual_edited'] === 1) && !$forceMetadata;
                    if ($manualProtected) {
                        $manualProtectedCount++;
                        $updateTechnicalStmt->execute(array_merge([
                            ':duration' => $metadata['duration'],
                            ':bitrate' => $metadata['bitrate'],
                            ':sample_rate' => $metadata['sample_rate'],
                            ':channels' => $metadata['channels'],
                            ':cover_hash' => $metadata['cover_hash'],
                            ':cover_mime' => $metadata['cover_mime'],
                            ':has_cover' => $metadata['has_cover'],
                            ':metadata_source' => $metadata['metadata_source'],
                            ':metadata_error' => $metadata['metadata_error'],
                        ], $fileParams));
                    } else {
                        $updateFullStmt->execute(array_merge(scanMetadataParams($metadata), $fileParams));
                    }
                    $updatedCount++;
                } elseif ($existingSong['is_missing'] == 1) {
                    $markActiveStmt->execute([':file_hash' => $fileHash]);
                    $updatedCount++;
                } else {
                    $skippedCount++;
                }
            } else {
                $insertStmt->execute(array_merge(scanMetadataParams($metadata), $fileParams));
                $newCount++;
            }
        } catch (Throwable $fileEx) {
            $errorCount++;
            scanLogMessage('File processing error: ' . toUtf8Path($winFilePath) . ' / ' . $fileEx->getMessage(), 'WARNING');
        }
    }

    $activeSongs = $pdo->query("SELECT id, file_hash, file_path, title FROM songs WHERE is_missing = 0")->fetchAll();
    $markMissingStmt = $pdo->prepare("UPDATE songs SET is_missing = 1 WHERE id = :id");
    $cleanScanPath = strtolower(str_replace('\\', '/', $scanPath));
    foreach ($activeSongs as $song) {
        $cleanPath = strtolower(str_replace('\\', '/', $song['file_path']));
        if (strpos($cleanPath, $cleanScanPath) === 0 && !in_array($song['file_hash'], $foundHashes, true)) {
            $markMissingStmt->execute([':id' => $song['id']]);
            $deletedCount++;
        }
    }

    $scanFinishedAt = date('Y-m-d H:i:s');
    $elapsedTime = round(microtime(true) - $startTime, 2);
    $message = sprintf('Scan completed. Found: %d, new: %d, updated: %d, skipped: %d, missing: %d, tags: %d, filename: %d, manual protected: %d, cover: %d, no cover: %d, metadata errors: %d, file errors: %d, elapsed: %ss', $totalFound, $newCount, $updatedCount, $skippedCount, $deletedCount, $tagCount, $filenameCount, $manualProtectedCount, $coverSuccessCount, $coverMissingCount, $metadataErrorCount, $errorCount, $elapsedTime);
    scanLogMessage($message);

    $logInsertStmt = $pdo->prepare("
        INSERT INTO scan_logs (
            scan_started_at, scan_finished_at, total_found, new_count, updated_count, deleted_count,
            skipped_count, tag_count, filename_count, manual_protected_count, cover_success_count,
            cover_missing_count, metadata_error_count, missing_count, message
        ) VALUES (
            :scan_started_at, :scan_finished_at, :total_found, :new_count, :updated_count, :deleted_count,
            :skipped_count, :tag_count, :filename_count, :manual_protected_count, :cover_success_count,
            :cover_missing_count, :metadata_error_count, :missing_count, :message
        )
    ");
    $logInsertStmt->execute([
        ':scan_started_at' => $scanStartedAt,
        ':scan_finished_at' => $scanFinishedAt,
        ':total_found' => $totalFound,
        ':new_count' => $newCount,
        ':updated_count' => $updatedCount,
        ':deleted_count' => $deletedCount,
        ':skipped_count' => $skippedCount,
        ':tag_count' => $tagCount,
        ':filename_count' => $filenameCount,
        ':manual_protected_count' => $manualProtectedCount,
        ':cover_success_count' => $coverSuccessCount,
        ':cover_missing_count' => $coverMissingCount,
        ':metadata_error_count' => $metadataErrorCount,
        ':missing_count' => $deletedCount,
        ':message' => $message,
    ]);
} catch (Throwable $e) {
    $message = 'Scan failed: ' . $e->getMessage();
    scanLogMessage($message, 'ERROR');
    try {
        $pdo = getDBConnection();
        $logInsertStmt = $pdo->prepare("INSERT INTO scan_logs (scan_started_at, scan_finished_at, message) VALUES (:scan_started_at, :scan_finished_at, :message)");
        $logInsertStmt->execute([
            ':scan_started_at' => $scanStartedAt,
            ':scan_finished_at' => date('Y-m-d H:i:s'),
            ':message' => $message,
        ]);
    } catch (Throwable $ignored) {
    }
    if (!$isCli) {
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['success' => false, 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
        exit;
    }
}

if (!$isCli) {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'success' => $errorCount === 0,
        'message' => $message,
        'stats' => [
            'total' => $totalFound,
            'new' => $newCount,
            'updated' => $updatedCount,
            'skipped' => $skippedCount,
            'deleted' => $deletedCount,
            'errors' => $errorCount,
            'tag' => $tagCount,
            'filename' => $filenameCount,
            'manual_protected' => $manualProtectedCount,
            'cover' => $coverSuccessCount,
            'no_cover' => $coverMissingCount,
            'metadata_errors' => $metadataErrorCount,
        ],
    ], JSON_UNESCAPED_UNICODE);
}
