using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;

namespace Furlumin.SDK.Editor
{
    [Serializable]
    internal sealed class DownloadUrlResponse
    {
        public string url;
        public string expiresAt;
    }

    internal static class FurluminAssetDownloader
    {
        private const int DownloadTimeoutSeconds = 300;

        internal static async Task<bool> DownloadAndImport(AssetInfo asset)
        {
            string tempPath = null;
            try
            {
                EditorUtility.DisplayProgressBar("Furlumin SDK",
                    "Requesting download link: " + asset.name + "...", 0f);

                var dl = await FurluminAPIClient.GetAsync("/api/sdk/download/" + asset.id, authorized: true);
                if (!dl.success)
                {
                    EditorUtility.DisplayDialog("Furlumin SDK",
                        $"Failed to request download link (HTTP {dl.statusCode}).", "OK");
                    return false;
                }

                DownloadUrlResponse urlResp;
                try   { urlResp = JsonUtility.FromJson<DownloadUrlResponse>(dl.body); }
                catch { urlResp = null; }

                if (urlResp == null || string.IsNullOrEmpty(urlResp.url))
                {
                    EditorUtility.DisplayDialog("Furlumin SDK", "Invalid response from server.", "OK");
                    return false;
                }

                if (!urlResp.url.StartsWith("https://api.furlumin.de/"))
                {
                    EditorUtility.DisplayDialog("Furlumin SDK", "Untrusted download source blocked.", "OK");
                    return false;
                }

                tempPath = Path.Combine(
                    Application.temporaryCachePath,
                    $"furlumin_{SanitizeFileName(asset.name)}_{SanitizeFileName(asset.version)}.unitypackage");

                if (File.Exists(tempPath))
                    File.Delete(tempPath);

                using (var req = UnityWebRequest.Get(urlResp.url))
                {
                    req.downloadHandler = new DownloadHandlerFile(tempPath);
                    req.timeout         = DownloadTimeoutSeconds;
                    var op = req.SendWebRequest();

                    while (!op.isDone)
                    {
                        float progress = req.downloadProgress;
                        long  received = (long)(asset.sizeBytes * progress);
                        string label   = asset.sizeBytes > 0
                            ? $"Downloading: {asset.name}  {FormatBytes(received)} / {FormatBytes(asset.sizeBytes)}"
                            : $"Downloading: {asset.name}...";

                        bool cancelled = EditorUtility.DisplayCancelableProgressBar(
                            "Furlumin SDK", label, Mathf.Clamp01(progress));

                        if (cancelled)
                        {
                            req.Abort();
                            DeleteTemp(tempPath);
                            return false;
                        }
                        await Task.Yield();
                    }

                    if (req.result != UnityWebRequest.Result.Success)
                    {
                        DeleteTemp(tempPath);
                        EditorUtility.DisplayDialog("Furlumin SDK",
                            $"Download failed: {req.error}", "OK");
                        return false;
                    }
                }

                if (asset.sizeBytes > 0 && File.Exists(tempPath))
                {
                    long actual = new FileInfo(tempPath).Length;
                    if (actual != asset.sizeBytes)
                    {
                        DeleteTemp(tempPath);
                        EditorUtility.DisplayDialog("Furlumin SDK",
                            $"File integrity check failed.\nExpected {FormatBytes(asset.sizeBytes)}, got {FormatBytes(actual)}.\nThe file may be corrupted.", "OK");
                        return false;
                    }
                }

                if (!string.IsNullOrEmpty(asset.checksum) && File.Exists(tempPath))
                {
                    EditorUtility.DisplayProgressBar("Furlumin SDK", "Verifying file integrity...", 0.97f);
                    string actualHash = ComputeSHA256(tempPath);
                    if (!string.Equals(actualHash, asset.checksum, StringComparison.OrdinalIgnoreCase))
                    {
                        DeleteTemp(tempPath);
                        EditorUtility.DisplayDialog("Furlumin SDK",
                            "Checksum mismatch - the file may have been tampered with.\nDownload aborted.", "OK");
                        return false;
                    }
                }

                EditorUtility.DisplayProgressBar("Furlumin SDK", "Importing " + asset.name + "...", 1f);
                AssetDatabase.ImportPackage(tempPath, true);
                return true;
            }
            finally
            {
                EditorUtility.ClearProgressBar();
            }
        }

        private static string ComputeSHA256(string filePath)
        {
            using var sha = SHA256.Create();
            using var fs  = File.OpenRead(filePath);
            byte[] hash   = sha.ComputeHash(fs);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }

        private static void DeleteTemp(string path)
        {
            try { if (!string.IsNullOrEmpty(path) && File.Exists(path)) File.Delete(path); }
            catch { }
        }

        private static string SanitizeFileName(string name)
        {
            if (string.IsNullOrEmpty(name)) return "asset";
            foreach (char c in Path.GetInvalidFileNameChars())
                name = name.Replace(c, '_');
            return name;
        }

        private static string FormatBytes(long bytes)
        {
            if (bytes <= 0) return "0 B";
            string[] u = { "B", "KB", "MB", "GB" };
            double s = bytes;
            int i = 0;
            while (s >= 1024 && i < u.Length - 1) { s /= 1024; i++; }
            return s.ToString("0.#") + " " + u[i];
        }
    }
}
