<?php
require_once dirname(__DIR__) . '/config/config.php';
require_once dirname(__DIR__) . '/includes/db.php';
require_once dirname(__DIR__) . '/includes/functions.php';
require_once dirname(__DIR__) . '/includes/auth.php';
requirePermission('duplicates_manage');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: duplicates.php?status=error&message=' . urlencode('중복곡 정리는 POST 방식으로만 실행됩니다.'));
    exit;
}
requireCsrfToken();

function duplicateRedirect($status, $message) {
    header('Location: duplicates.php?status=' . urlencode($status) . '&message=' . urlencode($message));
    exit;
}

function duplicateCleanIds($ids) {
    if (!is_array($ids)) return [];
    $ids = array_values(array_unique(array_filter(array_map('intval', $ids), static function ($id) {
        return $id > 0;
    })));
    return $ids;
}

function duplicateTableExists(PDO $pdo, string $table): bool {
    $stmt = $pdo->prepare("
        SELECT COUNT(*)
        FROM information_schema.tables
        WHERE table_schema = DATABASE()
          AND table_name = :table_name
    ");
    $stmt->execute([':table_name' => $table]);
    return (int)$stmt->fetchColumn() > 0;
}

function duplicatePlaceholders(array $ids): string {
    return implode(',', array_fill(0, count($ids), '?'));
}

function duplicateNormalizeMarker($value): string {
    $value = function_exists('ensureUtf8String') ? ensureUtf8String($value) : (string)$value;
    $normalized = preg_replace('/\s+/u', ' ', $value);
    if ($normalized === null) $normalized = (string)$value;
    $value = trim($normalized);
    return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
}

function duplicateBasename($path): string {
    $path = str_replace('\\', '/', (string)$path);
    return duplicateNormalizeMarker(basename($path));
}

function duplicateAllSameNonEmpty(array $values): bool {
    $values = array_values(array_filter($values, static function ($value) {
        return $value !== '';
    }));
    return count($values) >= 2 && count(array_unique($values)) === 1;
}

function duplicateIsPathInsideStrict($path, $baseDir): bool {
    $realPath = realpath($path) ?: $path;
    $realBase = realpath($baseDir) ?: $baseDir;
    if (!$realPath || !$realBase) return false;
    $realPath = rtrim(strtolower(str_replace('\\', '/', toUtf8Path($realPath))), '/');
    $realBase = rtrim(strtolower(str_replace('\\', '/', toUtf8Path($realBase))), '/');
    return $realPath === $realBase || strpos($realPath . '/', $realBase . '/') === 0;
}

function duplicateAssertCandidateGroup(PDO $pdo, array $ids, int $keepId = 0): void {
    $checkIds = $ids;
    if ($keepId > 0) $checkIds[] = $keepId;
    $checkIds = duplicateCleanIds($checkIds);
    if (count($checkIds) < 2) {
        throw new RuntimeException('중복 후보 그룹을 확인할 곡이 부족합니다.');
    }

    $placeholders = duplicatePlaceholders($checkIds);
    $stmt = $pdo->prepare("
        SELECT id, file_hash, file_size, title, artist, duration, file_path
        FROM songs
        WHERE id IN ($placeholders)
          AND is_missing = 0
          AND COALESCE(is_hidden, 0) = 0
    ");
    $stmt->execute($checkIds);
    $rows = $stmt->fetchAll();
    if (count($rows) !== count($checkIds)) {
        throw new RuntimeException('선택한 곡 중 정리할 수 없는 곡이 있습니다.');
    }

    $hashes = [];
    $sizeTitleArtist = [];
    $titleArtistDuration = [];
    $filenames = [];
    foreach ($rows as $row) {
        $hashes[] = duplicateNormalizeMarker($row['file_hash'] ?? '');
        $title = duplicateNormalizeMarker($row['title'] ?? '');
        $artist = duplicateNormalizeMarker($row['artist'] ?? '');
        $duration = (int)($row['duration'] ?? 0);
        $fileSize = (int)($row['file_size'] ?? 0);
        $sizeTitleArtist[] = ($fileSize > 0 && $title !== '') ? $fileSize . '|' . $title . '|' . $artist : '';
        $titleArtistDuration[] = ($title !== '' && $duration > 0) ? $title . '|' . $artist . '|' . $duration : '';
        $filenames[] = duplicateBasename($row['file_path'] ?? '');
    }

    if (
        duplicateAllSameNonEmpty($hashes) ||
        duplicateAllSameNonEmpty($sizeTitleArtist) ||
        duplicateAllSameNonEmpty($titleArtistDuration) ||
        duplicateAllSameNonEmpty($filenames)
    ) {
        return;
    }

    throw new RuntimeException('선택한 곡들이 같은 중복 후보 그룹으로 확인되지 않습니다.');
}

function duplicateDeleteSongRows(PDO $pdo, array $ids): int {
    if (empty($ids)) return 0;
    $placeholders = duplicatePlaceholders($ids);
    $relatedTables = [
        'playlist_songs',
        'liked_songs',
        'play_history',
        'lyrics',
        'track_notes',
        'track_ratings',
        'song_tags',
    ];
    foreach ($relatedTables as $table) {
        if (!duplicateTableExists($pdo, $table)) continue;
        $stmt = $pdo->prepare("DELETE FROM `$table` WHERE song_id IN ($placeholders)");
        $stmt->execute($ids);
    }
    $stmt = $pdo->prepare("DELETE FROM songs WHERE id IN ($placeholders)");
    $stmt->execute($ids);
    return $stmt->rowCount();
}

function duplicateDeletePhysicalFiles(PDO $pdo, array $ids): array {
    if (empty($ids)) return ['deleted_ids' => [], 'deleted_files' => 0, 'skipped' => 0];

    $placeholders = duplicatePlaceholders($ids);
    $stmt = $pdo->prepare("SELECT id, file_path, extension FROM songs WHERE id IN ($placeholders)");
    $stmt->execute($ids);
    $rows = $stmt->fetchAll();
    if (empty($rows)) {
        throw new RuntimeException('삭제할 곡을 찾을 수 없습니다.');
    }

    $scanRootPath = resolveFilesystemPath(getScanPathSetting(), 'dir') ?? toWinPath(getScanPathSetting());
    $scanRoot = $scanRootPath ? (realpath($scanRootPath) ?: $scanRootPath) : false;
    if (!$scanRoot || !is_dir($scanRoot)) {
        throw new RuntimeException('스캔 폴더를 확인할 수 없어 원본 파일을 삭제하지 않았습니다.');
    }
    $allowed = array_map('strtolower', getAllowedExtensionsSetting());
    $deletedIds = [];
    $skipped = 0;

    foreach ($rows as $row) {
        $id = (int)($row['id'] ?? 0);
        $filePath = (string)($row['file_path'] ?? '');
        $winPath = $filePath !== '' ? resolveFilesystemPath($filePath, 'file') : null;
        $realFile = $winPath !== null ? (realpath($winPath) ?: $winPath) : false;
        $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION) ?: (string)($row['extension'] ?? ''));

        if ($id <= 0 || !$realFile || !is_file($realFile) || !duplicateIsPathInsideStrict($realFile, $scanRoot) || !in_array($ext, $allowed, true)) {
            $skipped++;
            continue;
        }
        if (!@unlink($realFile)) {
            throw new RuntimeException('일부 원본 파일을 삭제하지 못했습니다. 파일 권한 또는 사용 중 상태를 확인해 주세요.');
        }
        $deletedIds[] = $id;
    }

    return [
        'deleted_ids' => $deletedIds,
        'deleted_files' => count($deletedIds),
        'skipped' => $skipped,
    ];
}

function duplicateCopyUniqueText(PDO $pdo, string $table, string $column, int $keepId, array $sourceIds): void {
    if (!duplicateTableExists($pdo, $table) || empty($sourceIds)) return;
    $keepStmt = $pdo->prepare("SELECT $column FROM `$table` WHERE song_id = :song_id LIMIT 1");
    $keepStmt->execute([':song_id' => $keepId]);
    $keepValue = trim((string)($keepStmt->fetchColumn() ?: ''));
    if ($keepValue !== '') return;

    $placeholders = duplicatePlaceholders($sourceIds);
    $sourceStmt = $pdo->prepare("
        SELECT $column
        FROM `$table`
        WHERE song_id IN ($placeholders)
          AND $column IS NOT NULL
          AND TRIM($column) <> ''
        ORDER BY updated_at DESC, id DESC
        LIMIT 1
    ");
    $sourceStmt->execute($sourceIds);
    $sourceValue = $sourceStmt->fetchColumn();
    if (trim((string)$sourceValue) === '') return;

    $insert = $pdo->prepare("
        INSERT INTO `$table` (song_id, $column)
        VALUES (:song_id, :value)
        ON DUPLICATE KEY UPDATE $column = VALUES($column), updated_at = NOW()
    ");
    $insert->execute([
        ':song_id' => $keepId,
        ':value' => $sourceValue,
    ]);
}

function duplicateMergeIntoKeep(PDO $pdo, int $keepId, array $sourceIds): int {
    if ($keepId <= 0 || empty($sourceIds)) return 0;
    $sourceIds = array_values(array_diff($sourceIds, [$keepId]));
    if (empty($sourceIds)) return 0;

    $allIds = array_merge([$keepId], $sourceIds);
    $placeholders = duplicatePlaceholders($allIds);
    $check = $pdo->prepare("SELECT id FROM songs WHERE id IN ($placeholders)");
    $check->execute($allIds);
    $existing = array_map('intval', $check->fetchAll(PDO::FETCH_COLUMN));
    if (!in_array($keepId, $existing, true)) {
        throw new RuntimeException('대표곡을 찾을 수 없습니다.');
    }
    $sourceIds = array_values(array_intersect($sourceIds, $existing));
    if (empty($sourceIds)) {
        throw new RuntimeException('병합할 중복 곡을 찾을 수 없습니다.');
    }
    $sourcePlaceholders = duplicatePlaceholders($sourceIds);

    $sumStmt = $pdo->prepare("SELECT COALESCE(SUM(play_count), 0) FROM songs WHERE id IN ($sourcePlaceholders)");
    $sumStmt->execute($sourceIds);
    $sourcePlayCount = (int)$sumStmt->fetchColumn();
    if ($sourcePlayCount > 0) {
        $stmt = $pdo->prepare("UPDATE songs SET play_count = play_count + :play_count, updated_at = NOW() WHERE id = :id");
        $stmt->execute([':play_count' => $sourcePlayCount, ':id' => $keepId]);
    }

    if (duplicateTableExists($pdo, 'play_history')) {
        $stmt = $pdo->prepare("UPDATE play_history SET song_id = ? WHERE song_id IN ($sourcePlaceholders)");
        $stmt->execute(array_merge([$keepId], $sourceIds));
    }

    if (duplicateTableExists($pdo, 'liked_songs')) {
        $stmt = $pdo->prepare("INSERT IGNORE INTO liked_songs (song_id) SELECT ? FROM liked_songs WHERE song_id IN ($sourcePlaceholders) LIMIT 1");
        $stmt->execute(array_merge([$keepId], $sourceIds));
        $stmt = $pdo->prepare("DELETE FROM liked_songs WHERE song_id IN ($sourcePlaceholders)");
        $stmt->execute($sourceIds);
    }

    if (duplicateTableExists($pdo, 'playlist_songs')) {
        $stmt = $pdo->prepare("
            INSERT IGNORE INTO playlist_songs (playlist_id, song_id, sort_order)
            SELECT playlist_id, ?, sort_order
            FROM playlist_songs
            WHERE song_id IN ($sourcePlaceholders)
        ");
        $stmt->execute(array_merge([$keepId], $sourceIds));
        $stmt = $pdo->prepare("DELETE FROM playlist_songs WHERE song_id IN ($sourcePlaceholders)");
        $stmt->execute($sourceIds);
    }

    if (duplicateTableExists($pdo, 'song_tags')) {
        $stmt = $pdo->prepare("
            INSERT IGNORE INTO song_tags (song_id, tag_id)
            SELECT ?, tag_id
            FROM song_tags
            WHERE song_id IN ($sourcePlaceholders)
        ");
        $stmt->execute(array_merge([$keepId], $sourceIds));
        $stmt = $pdo->prepare("DELETE FROM song_tags WHERE song_id IN ($sourcePlaceholders)");
        $stmt->execute($sourceIds);
    }

    duplicateCopyUniqueText($pdo, 'lyrics', 'lyrics_text', $keepId, $sourceIds);
    duplicateCopyUniqueText($pdo, 'track_notes', 'note_text', $keepId, $sourceIds);

    if (duplicateTableExists($pdo, 'track_ratings')) {
        $ratingStmt = $pdo->prepare("SELECT rating FROM track_ratings WHERE song_id = :song_id LIMIT 1");
        $ratingStmt->execute([':song_id' => $keepId]);
        $keepRating = (int)($ratingStmt->fetchColumn() ?: 0);
        if ($keepRating <= 0) {
            $stmt = $pdo->prepare("SELECT MAX(rating) FROM track_ratings WHERE song_id IN ($sourcePlaceholders)");
            $stmt->execute($sourceIds);
            $sourceRating = (int)($stmt->fetchColumn() ?: 0);
            if ($sourceRating > 0) {
                $insert = $pdo->prepare("
                    INSERT INTO track_ratings (song_id, rating)
                    VALUES (:song_id, :rating)
                    ON DUPLICATE KEY UPDATE rating = VALUES(rating), updated_at = NOW()
                ");
                $insert->execute([':song_id' => $keepId, ':rating' => $sourceRating]);
            }
        }
        $stmt = $pdo->prepare("DELETE FROM track_ratings WHERE song_id IN ($sourcePlaceholders)");
        $stmt->execute($sourceIds);
    }

    foreach (['lyrics', 'track_notes'] as $table) {
        if (!duplicateTableExists($pdo, $table)) continue;
        $stmt = $pdo->prepare("DELETE FROM `$table` WHERE song_id IN ($sourcePlaceholders)");
        $stmt->execute($sourceIds);
    }

    $stmt = $pdo->prepare("DELETE FROM songs WHERE id IN ($sourcePlaceholders)");
    $stmt->execute($sourceIds);
    return $stmt->rowCount();
}

try {
    $action = (string)($_POST['action'] ?? '');
    $ids = duplicateCleanIds($_POST['song_ids'] ?? []);
    $pdo = getDBConnection();

    if (empty($ids)) {
        throw new RuntimeException('선택한 곡이 없습니다.');
    }

    if ($action === 'hide_selected') {
        duplicateAssertCandidateGroup($pdo, $ids, (int)($_POST['keep_id'] ?? 0));
        $placeholders = duplicatePlaceholders($ids);
        $stmt = $pdo->prepare("UPDATE songs SET is_hidden = 1, updated_at = NOW() WHERE id IN ($placeholders)");
        $stmt->execute($ids);
        $message = number_format($stmt->rowCount()) . '곡을 일반 목록에서 숨김 처리했습니다.';
        if (function_exists('logAdminAction')) logAdminAction('duplicate_songs_hidden', 'Duplicate songs hidden: ' . count($ids));
        duplicateRedirect('success', $message);
    }

    if ($action === 'delete_selected') {
        duplicateAssertCandidateGroup($pdo, $ids, (int)($_POST['keep_id'] ?? 0));
        $pdo->beginTransaction();
        try {
            $deleted = duplicateDeleteSongRows($pdo, $ids);
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
        if (function_exists('writeAuditLog')) {
            writeAuditLog('duplicate_songs_deleted', '중복 후보 곡 DB 삭제: ' . $deleted . '곡', 'success', 'warning', 'song', 0);
        } elseif (function_exists('logAdminAction')) {
            logAdminAction('duplicate_songs_deleted', 'Duplicate candidate songs deleted: ' . $deleted);
        }
        duplicateRedirect('success', '선택한 중복 후보 ' . number_format($deleted) . '곡과 연결 데이터를 삭제했습니다. 원본 음악 파일은 삭제하지 않았습니다.');
    }

    if ($action === 'delete_files_selected') {
        $confirmPhrase = trim((string)($_POST['confirm_phrase'] ?? ''));
        if ($confirmPhrase !== '파일삭제') {
            throw new RuntimeException('원본 파일 삭제 확인 문구가 일치하지 않습니다.');
        }
        $keepId = (int)($_POST['keep_id'] ?? 0);
        if ($keepId > 0 && in_array($keepId, $ids, true)) {
            throw new RuntimeException('대표곡은 원본 파일 삭제 대상에서 제외하세요.');
        }
        duplicateAssertCandidateGroup($pdo, $ids, $keepId);

        $result = duplicateDeletePhysicalFiles($pdo, $ids);
        $deletedIds = $result['deleted_ids'];
        if (empty($deletedIds)) {
            throw new RuntimeException('삭제 가능한 원본 파일이 없습니다.');
        }

        $pdo->beginTransaction();
        try {
            $deletedRows = duplicateDeleteSongRows($pdo, $deletedIds);
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }

        if (function_exists('writeAuditLog')) {
            writeAuditLog('duplicate_files_deleted', '중복 후보 원본 파일 삭제: ' . count($deletedIds) . '개 파일, DB ' . $deletedRows . '곡 정리', 'success', 'critical', 'song', 0);
        } elseif (function_exists('logAdminAction')) {
            logAdminAction('duplicate_files_deleted', 'Duplicate source files deleted: ' . count($deletedIds));
        }
        duplicateRedirect('success', '선택한 중복 후보 원본 파일 ' . number_format((int)$result['deleted_files']) . '개를 삭제하고 DB 연결 데이터를 정리했습니다. 건너뜀 ' . number_format((int)$result['skipped']) . '개.');
    }

    if ($action === 'merge_selected') {
        $keepId = (int)($_POST['keep_id'] ?? 0);
        if ($keepId <= 0) {
            throw new RuntimeException('대표곡을 선택하세요.');
        }
        duplicateAssertCandidateGroup($pdo, $ids, $keepId);
        $sourceIds = array_values(array_diff($ids, [$keepId]));
        if (empty($sourceIds)) {
            throw new RuntimeException('대표곡 외에 병합할 곡을 1개 이상 선택하세요.');
        }
        $pdo->beginTransaction();
        try {
            $merged = duplicateMergeIntoKeep($pdo, $keepId, $sourceIds);
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
        if (function_exists('writeAuditLog')) {
            writeAuditLog('duplicate_songs_merged', '중복 후보 병합: 대표곡 #' . $keepId . ', 제거 ' . $merged . '곡', 'success', 'warning', 'song', $keepId);
        } elseif (function_exists('logAdminAction')) {
            logAdminAction('duplicate_songs_merged', 'Duplicate songs merged into #' . $keepId . ': ' . $merged);
        }
        duplicateRedirect('success', '대표곡 #' . $keepId . ' 기준으로 ' . number_format($merged) . '곡을 병합했습니다. 원본 음악 파일은 삭제하지 않았습니다.');
    }

    throw new RuntimeException('알 수 없는 작업입니다.');
} catch (Throwable $e) {
    if (function_exists('writeAppLog')) {
        writeAppLog('error', 'duplicate_action_error', 'Duplicate management action failed', [
            'action' => $_POST['action'] ?? '',
            'error' => $e->getMessage(),
        ]);
    }
    $message = function_exists('maskInternalPathText') ? maskInternalPathText($e->getMessage()) : $e->getMessage();
    if (preg_match('/SQLSTATE|PDOException|Stack trace|Fatal error|Warning:|Notice:|[A-Z]:[\\\\\/]/i', $message)) {
        $message = '중복곡 정리 중 오류가 발생했습니다. 오류 로그를 확인해 주세요.';
    }
    duplicateRedirect('error', $message);
}
