/*************************************************************************************************
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 *
 * Use of this source code is governed by the Inworld.ai Software Development Kit License Agreement
 * that can be found in the LICENSE.md file or at https://www.inworld.ai/sdk-license
 *************************************************************************************************/
#if UNITY_EDITOR
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using UnityEngine;

namespace Inworld
{
    [Serializable]
    internal class EssentialPackageManifest
    {
        public string url;
        public string unitypackage;
        public string version;
        public string buildId;
        public string fileName;
        public string updatedAt;
        public string notes;

        public string PackageUrl => url?.Trim();
        public string UpdatePackageUrl => unitypackage?.Trim();

        public string VersionLabel
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(version))
                    return version.Trim();
                if (!string.IsNullOrWhiteSpace(buildId))
                    return buildId.Trim();
                return string.Empty;
            }
        }

        public string PackageDisplayName
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(fileName))
                    return fileName.Trim();

                if (Uri.TryCreate(PackageUrl, UriKind.Absolute, out Uri uri))
                    return Path.GetFileNameWithoutExtension(uri.AbsolutePath);

                return "InworldEssential";
            }
        }

        public string UpdatePackageDisplayName
        {
            get
            {
                if (Uri.TryCreate(UpdatePackageUrl, UriKind.Absolute, out Uri uri))
                    return Path.GetFileNameWithoutExtension(uri.AbsolutePath);

                return "InworldRuntime";
            }
        }

    }

    internal readonly struct EssentialManifestSnapshot
    {
        public EssentialManifestSnapshot(EssentialPackageManifest manifest, string rawJson)
        {
            Manifest = manifest;
            RawJson = rawJson;
        }

        public EssentialPackageManifest Manifest { get; }
        public string RawJson { get; }
    }

    internal static class EssentialPackageManifestStore
    {
        const string k_EssentialManifestUrl =
            "https://storage.googleapis.com/assets-inworld-ai/unity-packages/InworldEssential.json";
        const string k_CacheDirectoryRelativePath = "Assets/InworldRuntime/Data";
        const string k_CacheFileName = "EssentialManifestCache.json";

        public static string ManifestUrl => k_EssentialManifestUrl;
        static string ProjectRoot => Directory.GetParent(Application.dataPath)?.FullName ?? Application.dataPath;
        static string CacheDirectoryPath => Path.Combine(ProjectRoot, k_CacheDirectoryRelativePath.Replace('/', Path.DirectorySeparatorChar));
        static string CacheFilePath => Path.Combine(CacheDirectoryPath, k_CacheFileName);

        public static async Task<EssentialManifestSnapshot> FetchRemoteAsync()
        {
            using HttpClient httpClient = new HttpClient();
            string requestUrl = $"{k_EssentialManifestUrl}?ts={DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
            string rawJson = await httpClient.GetStringAsync(requestUrl);
            return ParseSnapshot(rawJson);
        }

        public static bool TryLoadCached(out EssentialManifestSnapshot snapshot)
        {
            snapshot = default;
            if (!File.Exists(CacheFilePath))
                return false;

            try
            {
                string rawJson = File.ReadAllText(CacheFilePath);
                snapshot = ParseSnapshot(rawJson);
                return true;
            }
            catch (Exception exception)
            {
                Debug.LogWarning($"[Inworld] Failed to read cached Essential manifest: {exception.Message}");
                return false;
            }
        }

        public static void SaveCache(EssentialManifestSnapshot snapshot)
        {
            if (string.IsNullOrWhiteSpace(snapshot.RawJson))
                throw new InvalidDataException("Essential manifest cache cannot be empty.");

            Directory.CreateDirectory(CacheDirectoryPath);
            File.WriteAllText(CacheFilePath, snapshot.RawJson);
        }

        public static bool HasNewerVersion(EssentialManifestSnapshot localSnapshot, EssentialManifestSnapshot remoteSnapshot)
        {
            string localVersion = localSnapshot.Manifest?.VersionLabel;
            string remoteVersion = remoteSnapshot.Manifest?.VersionLabel;

            if (TryParseVersion(remoteVersion, out Version remoteParsed))
            {
                if (TryParseVersion(localVersion, out Version localParsed))
                    return remoteParsed > localParsed;
                return true;
            }

            if (!string.IsNullOrWhiteSpace(remoteVersion) && !string.Equals(localVersion, remoteVersion, StringComparison.Ordinal))
                return true;

            return !string.Equals(localSnapshot.RawJson, remoteSnapshot.RawJson, StringComparison.Ordinal);
        }

        static EssentialManifestSnapshot ParseSnapshot(string rawJson)
        {
            if (string.IsNullOrWhiteSpace(rawJson))
                throw new InvalidDataException("Essential manifest is empty.");

            EssentialPackageManifest manifest;
            try
            {
                manifest = JsonUtility.FromJson<EssentialPackageManifest>(rawJson);
            }
            catch (Exception exception)
            {
                throw new InvalidDataException($"Essential manifest JSON is invalid: {exception.Message}", exception);
            }

            if (manifest == null)
                throw new InvalidDataException("Essential manifest JSON could not be parsed.");
            if (string.IsNullOrWhiteSpace(manifest.PackageUrl))
                throw new InvalidDataException("Essential manifest is missing 'url'.");
            if (!Uri.TryCreate(manifest.PackageUrl, UriKind.Absolute, out _))
                throw new InvalidDataException("Essential manifest 'url' is not a valid absolute URL.");
            if (!string.IsNullOrWhiteSpace(manifest.UpdatePackageUrl) &&
                !Uri.TryCreate(manifest.UpdatePackageUrl, UriKind.Absolute, out _))
                throw new InvalidDataException("Essential manifest 'unitypackage' is not a valid absolute URL.");

            return new EssentialManifestSnapshot(manifest, rawJson.Trim());
        }

        static bool TryParseVersion(string versionString, out Version version)
        {
            version = null;
            if (string.IsNullOrWhiteSpace(versionString))
                return false;

            string[] parts = versionString.Trim().Split('.');
            if (parts.Length == 0 || parts.Length > 4)
                return false;

            int[] numbers = new int[4];
            for (int i = 0; i < parts.Length; i++)
            {
                if (!int.TryParse(parts[i], out numbers[i]))
                    return false;
            }

            version = new Version(numbers[0], numbers[1], numbers[2], numbers[3]);
            return true;
        }
    }
}
#endif
