using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

// Beat Sage API 연동 (beatsage.com 역분석 기반)
// 출처: BadgerHobbs/BeatSage-Downloader 소스코드
public class BeatSageUploader : MonoBehaviour
{
    private const string BASE_URL      = "https://beatsage.com";
    private const string CREATE_EP     = "/beatsaber_custom_level_create";
    private const string HEARTBEAT_EP  = "/beatsaber_custom_level_heartbeat/{0}"; // {0} = levelId
    private const string DOWNLOAD_EP   = "/beatsaber_custom_level_download/{0}";  // {0} = levelId

    private const float POLL_INTERVAL  = 5f;
    private const float POLL_TIMEOUT   = 300f;

    // Beat Sage 난이도 이름 매핑 (내부 → API)
    // Beat Sage API가 인정하는 난이도: Normal, Hard, Expert, ExpertPlus (Easy 없음)
    private static readonly Dictionary<string, string> DiffNames = new()
    {
        { "normal", "Normal"     },
        { "hard",   "Hard"       },
        { "expert", "Expert"     },
        { "expertplus", "ExpertPlus" },
    };

    // Beat Sage .zip 내 .dat 파일명 매핑 (내부 → zip 내 파일명)
    private static readonly Dictionary<string, string> DatFileNames = new()
    {
        { "normal",     "Normal.dat"     },
        { "hard",       "Hard.dat"       },
        { "expert",     "Expert.dat"     },
        { "expertplus", "ExpertPlus.dat" },
    };

    public string CurrentStatus { get; private set; } = "";

    // ── Public API ───────────────────────────────────────────

    public IEnumerator Upload(
        string audioPath,
        List<string> difficulties,
        float bpm,
        Action<float> onProgress,
        Action<Dictionary<string, List<NoteData>>> onComplete,
        Action<string> onError)
    {
        // 1단계: 레벨 생성 요청
        SetStatus("[1/4] 음원 업로드 중...");
        Debug.Log($"[BeatSage] 업로드 시작 — 파일: {audioPath}");
        string levelId = null;

        yield return CreateLevel(audioPath, difficulties,
            id => levelId = id,
            onError);

        Debug.Log($"[BeatSage] CreateLevel 완료 — levelId: {levelId}");
        if (levelId == null) yield break;
        onProgress?.Invoke(0.15f);

        // 2단계: 생성 완료 폴링
        SetStatus("[2/4] AI 맵 생성 시작...");
        bool   ready   = false;
        float  elapsed = 0f;

        while (!ready && elapsed < POLL_TIMEOUT)
        {
            yield return new WaitForSeconds(POLL_INTERVAL);
            elapsed += POLL_INTERVAL;

            bool error = false;
            yield return PollHeartbeat(levelId,
                status =>
                {
                    ready = string.Equals(status, "generated", System.StringComparison.OrdinalIgnoreCase)
                         || string.Equals(status, "done",      System.StringComparison.OrdinalIgnoreCase);
                    error = string.Equals(status, "error",     System.StringComparison.OrdinalIgnoreCase);
                },
                onError);

            if (error) { onError?.Invoke("Beat Sage 생성 실패 (error 상태)"); yield break; }

            float progress = Mathf.Clamp01(elapsed / POLL_TIMEOUT);
            onProgress?.Invoke(0.15f + progress * 0.6f);
            SetStatus($"[2/4] AI 맵 생성 중... {(int)elapsed}초 경과");
        }

        if (!ready) { onError?.Invoke("Beat Sage 타임아웃 (5분 초과)"); yield break; }

        // 3단계: .zip 다운로드
        SetStatus("[3/4] 결과 다운로드 중...");
        byte[] zipBytes = null;

        yield return DownloadZip(levelId,
            bytes => zipBytes = bytes,
            onError);

        if (zipBytes == null) yield break;
        onProgress?.Invoke(0.9f);

        // 4단계: .zip 해제 + BeatSageConverter 변환
        SetStatus("[3/4] 맵 데이터 변환 중...");
        Dictionary<string, List<NoteData>> maps = null;

        try
        {
            maps = ExtractAndConvert(zipBytes, difficulties, bpm);
        }
        catch (Exception e)
        {
            onError?.Invoke($"변환 실패: {e.Message}");
            yield break;
        }

        onProgress?.Invoke(1f);
        SetStatus("[3/4] 변환 완료");
        onComplete?.Invoke(maps);
    }

    // ── Beat Sage API 요청 ────────────────────────────────────

    private IEnumerator CreateLevel(string audioPath, List<string> difficulties,
        Action<string> onSuccess, Action<string> onError)
    {
        byte[] audioBytes = File.ReadAllBytes(audioPath);
        string fileName   = Path.GetFileName(audioPath);

        // 난이도: 알 수 없는 값(easy 등)은 건너뜀, 쉼표 구분 단일 필드로 전송
        var mappedDiffs = new List<string>();
        foreach (string d in difficulties)
            if (DiffNames.TryGetValue(d, out var n)) mappedDiffs.Add(n);

        if (mappedDiffs.Count == 0)
        {
            onError?.Invoke("Beat Sage가 지원하지 않는 난이도입니다. Normal/Hard/Expert/ExpertPlus 중 선택하세요.");
            yield break;
        }

        string diffStr = string.Join(",", mappedDiffs);
        Debug.Log($"[BeatSage] 전송 difficulties: '{diffStr}'");

        var form = new List<IMultipartFormSection>
        {
            new MultipartFormFileSection("audio_file", audioBytes, fileName, "audio/mpeg"),
            new MultipartFormDataSection("audio_metadata_title",  " "),
            new MultipartFormDataSection("audio_metadata_artist", " "),
            new MultipartFormDataSection("difficulties",          diffStr),
            new MultipartFormDataSection("modes",                 "Standard"),
            new MultipartFormDataSection("events",                "DotBlocks,Obstacles,Bombs"),
            new MultipartFormDataSection("environment",           "DefaultEnvironment"),
            new MultipartFormDataSection("system_tag",            "v2"),
        };

        using var req = UnityWebRequest.Post(BASE_URL + CREATE_EP, form);
        req.SetRequestHeader("Accept", "*/*");
        req.SetRequestHeader("User-Agent", "VRBeatSaber/1.0");
        yield return req.SendWebRequest();

        if (req.result != UnityWebRequest.Result.Success)
        {
            string body = req.downloadHandler?.text ?? "(응답 없음)";
            Debug.LogError($"[BeatSage] HTTP {req.responseCode} — {req.error}\n응답 본문: {body}");
            onError?.Invoke($"레벨 생성 요청 실패: {req.error}");
            yield break;
        }

        string json    = req.downloadHandler.text;
        string levelId = ParseJsonString(json, "id");
        Debug.Log($"[BeatSageUploader] 생성 응답: {json}");

        if (string.IsNullOrEmpty(levelId))
        {
            onError?.Invoke($"levelId 파싱 실패. 응답: {json}");
            yield break;
        }

        onSuccess?.Invoke(levelId);
    }

    private IEnumerator PollHeartbeat(string levelId, Action<string> onStatus, Action<string> onError)
    {
        string url = BASE_URL + string.Format(HEARTBEAT_EP, levelId);
        using var req = UnityWebRequest.Get(url);
        yield return req.SendWebRequest();

        if (req.result != UnityWebRequest.Result.Success)
        {
            onError?.Invoke($"상태 확인 실패: {req.error}");
            yield break;
        }

        // 응답 예: { "status": "generated" } | { "status": "pending" } | { "status": "error" }
        string status = ParseJsonString(req.downloadHandler.text, "status");
        Debug.Log($"[BeatSageUploader] 상태: {status}");
        onStatus?.Invoke(status ?? "");
    }

    private IEnumerator DownloadZip(string levelId, Action<byte[]> onSuccess, Action<string> onError)
    {
        string url = BASE_URL + string.Format(DOWNLOAD_EP, levelId);
        using var req = UnityWebRequest.Get(url);
        yield return req.SendWebRequest();

        if (req.result != UnityWebRequest.Result.Success)
        {
            onError?.Invoke($"다운로드 실패: {req.error}");
            yield break;
        }

        onSuccess?.Invoke(req.downloadHandler.data);
    }

    // ── .zip 해제 + 변환 ──────────────────────────────────────

    private static Dictionary<string, List<NoteData>> ExtractAndConvert(
        byte[] zipBytes, List<string> difficulties, float bpm)
    {
        var result = new Dictionary<string, List<NoteData>>();

        using var ms      = new MemoryStream(zipBytes);
        using var archive = new ZipArchive(ms, ZipArchiveMode.Read);

        foreach (string diff in difficulties)
        {
            if (!DatFileNames.TryGetValue(diff, out string datName)) continue;

            // 대소문자 무시 검색
            ZipArchiveEntry entry = null;
            foreach (var e in archive.Entries)
            {
                if (string.Equals(e.Name, datName, StringComparison.OrdinalIgnoreCase))
                { entry = e; break; }
            }

            if (entry == null)
            {
                Debug.LogWarning($"[BeatSageUploader] {datName} 없음 — 건너뜀");
                continue;
            }

            using var reader = new StreamReader(entry.Open(), Encoding.UTF8);
            result[diff] = BeatSageConverter.Convert(reader.ReadToEnd(), bpm);
        }

        return result;
    }

    // ── 유틸 ─────────────────────────────────────────────────

    private static string ParseJsonString(string json, string key)
    {
        string search = $"\"{key}\":\"";
        int start = json.IndexOf(search, StringComparison.Ordinal);
        if (start < 0) return null;
        start += search.Length;
        int end = json.IndexOf('"', start);
        return end > start ? json.Substring(start, end - start) : null;
    }

    private void SetStatus(string msg) => CurrentStatus = msg;
}
