<?php
require_once __DIR__ . '/functions.php';

function diagnosticResult($status, $title, $message, $detail = null) {
    return ['status' => $status, 'title' => $title, 'message' => $message, 'detail' => $detail];
}

function checkPhpStatus() {
    return diagnosticResult('ok', 'PHP 실행 상태', 'PHP가 정상 실행 중입니다.', PHP_VERSION . ' / ' . PHP_OS);
}

function checkDatabaseConnection() {
    try {
        $pdo = getDBConnection();
        $pdo->query('SELECT 1')->fetchColumn();
        return diagnosticResult('ok', 'MySQL 연결', 'DB 연결이 정상입니다.', DB_NAME);
    } catch (Throwable $e) {
        return diagnosticResult('error', 'MySQL 연결', 'DB 연결에 실패했습니다.', $e->getMessage());
    }
}

function checkRequiredTables() {
    $required = ['songs', 'liked_songs', 'play_history', 'playlists', 'playlist_songs', 'lyrics', 'settings', 'scan_logs', 'maintenance_logs', 'admin_users', 'admin_logs', 'app_logs'];
    try {
        $pdo = getDBConnection();
        $missing = [];
        foreach ($required as $table) {
            $stmt = $pdo->prepare("SHOW TABLES LIKE :table_name");
            $stmt->execute([':table_name' => $table]);
            if (!$stmt->fetchColumn()) $missing[] = $table;
        }
        if ($missing) return diagnosticResult('error', '주요 테이블', '누락된 테이블이 있습니다.', implode(', ', $missing));
        return diagnosticResult('ok', '주요 테이블', '필수 테이블이 모두 존재합니다.', implode(', ', $required));
    } catch (Throwable $e) {
        return diagnosticResult('warning', '주요 테이블', '테이블 점검 중 오류가 발생했습니다.', $e->getMessage());
    }
}

function checkScanPath() {
    $scanPath = getScanPathSetting();
    $winPath = toWinPath($scanPath);
    if (!is_dir($winPath)) return diagnosticResult('error', '음악 스캔 경로', '스캔 경로를 찾을 수 없습니다.', $scanPath);
    if (!is_readable($winPath)) return diagnosticResult('error', '음악 스캔 경로', '스캔 경로를 읽을 수 없습니다.', $scanPath);
    return diagnosticResult('ok', '음악 스캔 경로', '스캔 경로가 존재하고 읽을 수 있습니다.', $scanPath);
}

function checkStorageWritable() {
    $paths = [
        'storage' => dirname(__DIR__) . '/storage',
        'storage/logs' => dirname(__DIR__) . '/storage/logs',
        'storage/backups' => getBackupDir(),
        'storage/covers' => getCoverCacheDir(),
    ];
    $bad = [];
    foreach ($paths as $label => $path) {
        if (!is_dir($path)) @mkdir($path, 0777, true);
        if (!is_dir($path) || !is_writable($path)) $bad[] = $label;
    }
    if ($bad) return diagnosticResult('error', 'storage 쓰기 권한', '쓰기 불가능한 폴더가 있습니다.', implode(', ', $bad));
    return diagnosticResult('ok', 'storage 쓰기 권한', '필요한 storage 폴더에 쓸 수 있습니다.', implode(', ', array_keys($paths)));
}

function checkGetId3Status() {
    $path = __DIR__ . '/getid3/getid3.php';
    if (file_exists($path)) return diagnosticResult('ok', 'getID3', 'getID3 파일이 존재합니다.', $path);
    return diagnosticResult('warning', 'getID3', 'getID3가 없어 내부 태그 분석은 제한됩니다. 파일명 fallback은 계속 동작합니다.', '선택 설치 항목');
}

function checkPwaFiles() {
    $missing = [];
    foreach (['manifest.json', 'service-worker.js', 'offline.php'] as $file) {
        if (!is_file(dirname(__DIR__) . '/' . $file)) $missing[] = $file;
    }
    if ($missing) return diagnosticResult('warning', 'PWA 파일', '일부 PWA 파일이 없습니다.', implode(', ', $missing));
    return diagnosticResult('ok', 'PWA 파일', 'manifest와 service worker 파일이 있습니다.', null);
}

function checkPwaPlayerStatus() {
    try {
        $root = dirname(__DIR__);
        $manifestPath = $root . '/manifest.json';
        $serviceWorkerPath = $root . '/service-worker.js';
        $layoutPath = __DIR__ . '/layout.php';
        $requiredFiles = [
            'manifest.json',
            'service-worker.js',
            'offline.php',
            'stream.php',
            'cover.php',
            'assets/img/icons/icon-192.png',
            'assets/img/icons/icon-512.png',
            'assets/img/default-cover.svg',
        ];
        $missing = [];
        foreach ($requiredFiles as $file) {
            if (!is_file($root . '/' . $file)) $missing[] = $file;
        }

        $manifestIssues = [];
        $manifest = null;
        if (is_file($manifestPath)) {
            $manifest = json_decode((string)file_get_contents($manifestPath), true);
            if (!is_array($manifest)) {
                $manifestIssues[] = 'manifest JSON 오류';
            } else {
                foreach (['name', 'short_name', 'start_url', 'scope', 'display', 'theme_color', 'background_color', 'icons'] as $key) {
                    if (!array_key_exists($key, $manifest) || $manifest[$key] === '' || $manifest[$key] === []) {
                        $manifestIssues[] = 'manifest.' . $key . ' 누락';
                    }
                }
            }
        }

        $layout = is_file($layoutPath) ? (string)file_get_contents($layoutPath) : '';
        $metaIssues = [];
        if (strpos($layout, 'apple-touch-icon') === false) $metaIssues[] = 'apple-touch-icon 누락';
        if (strpos($layout, 'theme-color') === false) $metaIssues[] = 'theme-color 누락';
        if (strpos($layout, 'apple-mobile-web-app-capable') === false) $metaIssues[] = 'iOS standalone meta 누락';
        if (strpos($layout, 'assets/js/pwa.js') === false) $metaIssues[] = 'PWA 설치 JS 로드 누락';

        $streamPath = $root . '/stream.php';
        $streamSource = is_file($streamPath) ? (string)file_get_contents($streamPath) : '';
        if (strpos($streamSource, 'X-MelonLite-Preview') === false) $metaIssues[] = '비로그인 30초 미리듣기 헤더 누락';
        if (strpos($streamSource, 'no-store') === false || strpos($streamSource, 'Vary: Cookie') === false) $metaIssues[] = 'stream no-cache 헤더 확인 필요';

        $cacheVersion = '확인 필요';
        if (is_file($serviceWorkerPath)) {
            $sw = (string)file_get_contents($serviceWorkerPath);
            if (preg_match("/CACHE_VERSION\\s*=\\s*['\\\"]([^'\\\"]+)/", $sw, $matches)) {
                $cacheVersion = $matches[1];
            }
        }

        $stmt = getDBConnection()->prepare("
            SELECT
                SUM(CASE WHEN log_type IN ('stream_error','stream') OR message LIKE :stream_like THEN 1 ELSE 0 END) AS stream_errors,
                SUM(CASE WHEN log_type = 'pwa_error' OR message LIKE :pwa_like THEN 1 ELSE 0 END) AS pwa_errors,
                SUM(CASE WHEN log_type = 'player_error' OR message LIKE :player_like THEN 1 ELSE 0 END) AS player_errors
            FROM app_logs
            WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
        ");
        $stmt->execute([
            ':stream_like' => '%stream%',
            ':pwa_like' => '%service worker%',
            ':player_like' => '%player%',
        ]);
        $row = $stmt->fetch() ?: [];
        $streamErrors = (int)($row['stream_errors'] ?? 0);
        $pwaErrors = (int)($row['pwa_errors'] ?? 0);
        $playerErrors = (int)($row['player_errors'] ?? 0);
        $detail = 'stream ' . number_format($streamErrors) . '건, PWA ' . number_format($pwaErrors) . '건, player ' . number_format($playerErrors) . '건';
        $issueParts = array_merge($missing, $manifestIssues, $metaIssues);
        $detail .= ' / cache ' . $cacheVersion . ' / guest preview 30초 / install keys localStorage';
        if ($issueParts) {
            return diagnosticResult('warning', 'PWA/플레이어 점검', '모바일/PWA에 필요한 일부 설정을 확인해야 합니다.', implode(', ', $issueParts) . ' / ' . $detail);
        }
        $status = ($streamErrors + $pwaErrors + $playerErrors) > 0 ? 'warning' : 'ok';
        return diagnosticResult($status, 'PWA/플레이어 점검', 'PWA 파일, manifest, iOS 메타, 최근 플레이어 오류를 확인했습니다.', $detail);
    } catch (Throwable $e) {
        return diagnosticResult('warning', 'PWA/플레이어 점검', 'PWA/플레이어 상태를 확인하지 못했습니다.', $e->getMessage());
    }
}

function countDuplicateSuspects() {
    try {
        return (int)getDBConnection()->query("
            SELECT COUNT(*) FROM (
                SELECT file_hash FROM songs
                WHERE file_hash IS NOT NULL AND file_hash != ''
                GROUP BY file_hash
                HAVING COUNT(*) > 1
            ) d
        ")->fetchColumn();
    } catch (Throwable $e) {
        return 0;
    }
}

function checkRecentErrors() {
    try {
        $count = (int)getDBConnection()->query("SELECT COUNT(*) FROM app_logs WHERE log_level IN ('error','critical') AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)")->fetchColumn();
        if ($count > 0) return diagnosticResult('warning', '최근 오류', '최근 7일 오류 로그가 있습니다.', $count . '건');
        return diagnosticResult('ok', '최근 오류', '최근 7일 오류 로그가 없습니다.', null);
    } catch (Throwable $e) {
        return diagnosticResult('warning', '최근 오류', '오류 로그를 확인할 수 없습니다.', $e->getMessage());
    }
}

function checkMusicStats() {
    try {
        $stats = getLibraryStats();
        $detail = '파일 없음 ' . number_format($stats['missing']) . '곡, 메타데이터 누락 ' . number_format($stats['metadata_missing']) . '곡, 중복 의심 ' . number_format(countDuplicateSuspects()) . '그룹';
        $status = ($stats['missing'] > 0 || $stats['metadata_missing'] > 0) ? 'warning' : 'ok';
        return diagnosticResult($status, '보관함 상태', '음악 보관함 통계를 확인했습니다.', $detail);
    } catch (Throwable $e) {
        return diagnosticResult('warning', '보관함 상태', '보관함 통계를 확인할 수 없습니다.', $e->getMessage());
    }
}

function checkRecommendationStatus() {
    try {
        $pdo = getDBConnection();
        $row = $pdo->query("
            SELECT
                (SELECT COUNT(*) FROM play_history) AS play_count,
                (SELECT COUNT(*) FROM liked_songs) AS liked_count,
                (SELECT COUNT(*) FROM track_ratings WHERE rating IS NOT NULL AND rating > 0) AS rated_count,
                (SELECT COUNT(DISTINCT song_id) FROM song_tags) AS tagged_count,
                (SELECT COUNT(*) FROM songs WHERE is_missing = 1) AS missing_count,
                (SELECT COUNT(*) FROM songs WHERE is_missing = 0 AND (genre IS NULL OR genre = '' OR genre = 'Unknown')) AS missing_genre_count
        ")->fetch() ?: [];
        $playCount = (int)($row['play_count'] ?? 0);
        $likedCount = (int)($row['liked_count'] ?? 0);
        $ratedCount = (int)($row['rated_count'] ?? 0);
        $taggedCount = (int)($row['tagged_count'] ?? 0);
        $missingCount = (int)($row['missing_count'] ?? 0);
        $missingGenreCount = (int)($row['missing_genre_count'] ?? 0);
        $candidateScore = $playCount + $likedCount + $ratedCount + $taggedCount;
        $detail = '재생 기록 ' . number_format($playCount) . '건, 좋아요 ' . number_format($likedCount) . '곡, 별점 ' . number_format($ratedCount) . '곡, 태그 ' . number_format($taggedCount) . '곡, 추천 제외 파일 없음 ' . number_format($missingCount) . '곡, 장르 누락 ' . number_format($missingGenreCount) . '곡';
        if ($candidateScore < 10) {
            return diagnosticResult('warning', '추천 데이터 상태', '추천에 활용할 취향 데이터가 아직 적습니다.', $detail);
        }
        return diagnosticResult('ok', '추천 데이터 상태', '자동 플레이리스트와 추천에 필요한 기본 데이터가 있습니다.', $detail);
    } catch (Throwable $e) {
        return diagnosticResult('warning', '추천 데이터 상태', '추천 데이터 상태를 확인하지 못했습니다.', $e->getMessage());
    }
}

function checkAdminSecurityStatus() {
    try {
        $pdo = getDBConnection();
        $admins = (int)$pdo->query("SELECT COUNT(*) FROM admin_users WHERE is_active = 1")->fetchColumn();
        if ($admins <= 0) return diagnosticResult('error', '관리자 보안', '활성 관리자 계정이 없습니다.', null);
        return diagnosticResult('ok', '관리자 보안', '활성 관리자 계정이 존재합니다.', $admins . '명');
    } catch (Throwable $e) {
        return diagnosticResult('warning', '관리자 보안', '관리자 계정 상태를 확인할 수 없습니다.', $e->getMessage());
    }
}

function checkEncodingStatus() {
    try {
        $pdo = getDBConnection();
        $phpCharset = (string)ini_get('default_charset');
        $connectionCharset = (string)$pdo->query("SELECT @@character_set_connection")->fetchColumn();
        $connectionCollation = (string)$pdo->query("SELECT @@collation_connection")->fetchColumn();

        $schemaStmt = $pdo->prepare("
            SELECT DEFAULT_CHARACTER_SET_NAME AS charset_name, DEFAULT_COLLATION_NAME AS collation_name
            FROM information_schema.SCHEMATA
            WHERE SCHEMA_NAME = DATABASE()
            LIMIT 1
        ");
        $schemaStmt->execute();
        $schema = $schemaStmt->fetch() ?: [];

        $tables = ['songs', 'lyrics', 'track_notes', 'tags', 'song_tags', 'playlists'];
        $placeholders = [];
        $params = [];
        foreach ($tables as $idx => $table) {
            $key = ':table_' . $idx;
            $placeholders[] = $key;
            $params[$key] = $table;
        }
        $tableStmt = $pdo->prepare("
            SELECT TABLE_NAME, TABLE_COLLATION
            FROM information_schema.TABLES
            WHERE TABLE_SCHEMA = DATABASE()
              AND TABLE_NAME IN (" . implode(',', $placeholders) . ")
            ORDER BY TABLE_NAME
        ");
        $tableStmt->execute($params);
        $tableRows = $tableStmt->fetchAll();

        $nonUtf8Tables = [];
        $tableDetails = [];
        foreach ($tableRows as $row) {
            $name = (string)($row['TABLE_NAME'] ?? '');
            $collation = (string)($row['TABLE_COLLATION'] ?? '');
            $tableDetails[] = $name . '=' . $collation;
            if (stripos($collation, 'utf8mb4') !== 0) {
                $nonUtf8Tables[] = $name . '(' . $collation . ')';
            }
        }

        $playerPath = dirname(__DIR__) . '/assets/js/player.js';
        $layoutPath = __DIR__ . '/layout.php';
        $playerLoaded = is_file($playerPath);
        $layout = is_file($layoutPath) ? (string)file_get_contents($layoutPath) : '';
        $hasMetaCharset = stripos($layout, 'charset="UTF-8"') !== false || stripos($layout, "charset='UTF-8'") !== false || stripos($layout, 'charset=UTF-8') !== false;

        $detail = 'PHP default_charset=' . ($phpCharset ?: '미설정')
            . ' / connection=' . $connectionCharset . '/' . $connectionCollation
            . ' / database=' . (($schema['charset_name'] ?? 'unknown') . '/' . ($schema['collation_name'] ?? 'unknown'))
            . ' / tables=' . implode(', ', $tableDetails)
            . ' / meta charset=' . ($hasMetaCharset ? 'ok' : 'missing')
            . ' / player.js=' . ($playerLoaded ? 'ok' : 'missing')
            . ' / JSON_UNESCAPED_UNICODE 적용';

        $issues = [];
        if (strtolower($connectionCharset) !== 'utf8mb4') $issues[] = 'DB 연결 charset 확인 필요';
        if (($schema['charset_name'] ?? '') !== 'utf8mb4') $issues[] = 'DB 기본 charset 확인 필요';
        if (!$hasMetaCharset) $issues[] = 'HTML meta charset 누락';
        if (!$playerLoaded) $issues[] = 'player.js 누락';
        if ($nonUtf8Tables) $issues[] = 'utf8mb4 외 table: ' . implode(', ', $nonUtf8Tables);

        if ($issues) {
            return diagnosticResult('warning', '인코딩/UTF-8 점검', 'UTF-8 설정 중 확인이 필요한 항목이 있습니다.', implode(' / ', $issues) . ' / ' . $detail);
        }
        return diagnosticResult('ok', '인코딩/UTF-8 점검', 'PHP, DB 연결, 주요 테이블, HTML/JSON 출력 기준을 점검했습니다.', $detail);
    } catch (Throwable $e) {
        return diagnosticResult('warning', '인코딩/UTF-8 점검', '인코딩 상태를 확인하지 못했습니다.', $e->getMessage());
    }
}

function getDiagnosticsResults() {
    return [
        checkPhpStatus(),
        checkDatabaseConnection(),
        checkRequiredTables(),
        checkScanPath(),
        checkStorageWritable(),
        checkGetId3Status(),
        checkPwaFiles(),
        checkPwaPlayerStatus(),
        checkEncodingStatus(),
        checkRecentErrors(),
        checkMusicStats(),
        checkRecommendationStatus(),
        checkAdminSecurityStatus(),
    ];
}

function getDiagnosticAlerts() {
    $alerts = [];
    foreach (getDiagnosticsResults() as $row) {
        if ($row['status'] !== 'ok') $alerts[] = $row;
    }
    return $alerts;
}
