using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.Profiling;

namespace CustomAssetBundleSystem
{
    /// <summary>
    /// Custom AssetBundle manager - replaces Unity Addressables
    /// Full manual control, no Unity 6 bugs, lightweight and fast
    /// </summary>
    public class RemoteAssetBundleManager : MonoBehaviour
    {
        private static RemoteAssetBundleManager _instance;
        public static RemoteAssetBundleManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    var go = new GameObject("[RemoteAssetBundleManager]");
                    _instance = go.AddComponent<RemoteAssetBundleManager>();
                    DontDestroyOnLoad(go);
                }
                return _instance;
            }
        }

        // Configuration
        [Header("Remote Catalog Settings")]
        public string catalogUrl = "https://assetbundle.uncaptured.co/catalog.json";
        public int downloadTimeout = 900; // seconds (15 minutes) for large bundles
        public int maxRetries = 3;
        [Tooltip("If progress does not change for this many seconds, abort and retry.")]
        public float downloadStallTimeout = 45f;
        [Tooltip("Base delay between retries (multiplied by attempt index).")]
        public float retryBackoffSeconds = 2f;

        [Header("Memory Management")]
        [Tooltip("Unloads the scene bundle and its dependencies once the scene is loaded to free GPU/CPU memory (recommended on mobile).")]
        public bool unloadSceneBundlesAfterLoad = true;
        [Tooltip("Calls Resources.UnloadUnusedAssets and GC.Collect before and after heavy scene loads.")]
        public bool runMemoryCleanupAroundSceneLoad = true;
        [Tooltip("Logs runtime memory usage after cleanup steps.")]
        public bool logMemoryStats = false;

        // State
        private RemoteAssetCatalog _catalog;
        private Dictionary<string, AssetBundle> _loadedBundles = new Dictionary<string, AssetBundle>();
        private Dictionary<string, Coroutine> _activeDownloads = new Dictionary<string, Coroutine>();
        private bool _isInitialized = false;

        // Events
        public event Action<bool, string> OnCatalogLoaded;
        public event Action<string, float> OnBundleDownloadProgress;
        public event Action<string, bool> OnBundleLoaded;

        void Awake()
        {
            if (_instance != null && _instance != this)
            {
                Destroy(gameObject);
                return;
            }
            _instance = this;
            DontDestroyOnLoad(gameObject);
        }

        #region Initialization

        /// <summary>
        /// Initialize the system by downloading the remote catalog
        /// </summary>
        public IEnumerator InitializeAsync()
        {
            if (_isInitialized)
            {
                Debug.Log("[AssetBundleManager] Already initialized");
                yield break;
            }

            Debug.Log($"[AssetBundleManager] Downloading catalog from: {catalogUrl}");

            bool success = false;
            string errorMessage = "";

            for (int attempt = 1; attempt <= maxRetries; attempt++)
            {
                using (UnityWebRequest www = UnityWebRequest.Get(catalogUrl))
                {
                    www.timeout = downloadTimeout;
                    yield return www.SendWebRequest();

                    if (www.result == UnityWebRequest.Result.Success)
                    {
                        try
                        {
                            string jsonText = www.downloadHandler.text;
                            _catalog = JsonUtility.FromJson<RemoteAssetCatalog>(jsonText);

                            if (_catalog != null && _catalog.bundles != null)
                            {
                                Debug.Log($"[AssetBundleManager] ✓ Catalog loaded successfully!");
                                Debug.Log($"  Version: {_catalog.version}");
                                Debug.Log($"  Base URL: {_catalog.baseUrl}");
                                Debug.Log($"  Bundles: {_catalog.bundles.Count}");

                                _isInitialized = true;
                                success = true;
                                break;
                            }
                            else
                            {
                                errorMessage = "Catalog JSON is invalid or empty";
                            }
                        }
                        catch (Exception ex)
                        {
                            errorMessage = $"JSON parsing failed: {ex.Message}";
                            Debug.LogError($"[AssetBundleManager] {errorMessage}");
                        }
                    }
                    else
                    {
                        errorMessage = $"Download failed: {www.error}";
                        Debug.LogWarning($"[AssetBundleManager] Attempt {attempt}/{maxRetries}: {errorMessage}");

                        if (attempt < maxRetries)
                        {
                            yield return new WaitForSeconds(2f); // Wait before retry
                        }
                    }
                }
            }

            OnCatalogLoaded?.Invoke(success, errorMessage);

            if (!success)
            {
                Debug.LogError($"[AssetBundleManager] ✗ Failed to load catalog after {maxRetries} attempts: {errorMessage}");
            }
        }

        public bool IsInitialized => _isInitialized;

        #endregion

        #region Bundle Loading

        /// <summary>
        /// Load an AssetBundle by name (with dependencies)
        /// </summary>
        public IEnumerator LoadBundleAsync(string bundleName, Action<AssetBundle> onComplete = null)
        {
            if (!_isInitialized)
            {
                Debug.LogError("[AssetBundleManager] Not initialized! Call InitializeAsync() first.");
                onComplete?.Invoke(null);
                yield break;
            }

            // Check if already loaded
            if (_loadedBundles.TryGetValue(bundleName, out AssetBundle cachedBundle))
            {
                Debug.Log($"[AssetBundleManager] Bundle '{bundleName}' already loaded (using cached)");
                onComplete?.Invoke(cachedBundle);
                yield break;
            }

            // Find bundle entry in catalog
            var bundleEntry = _catalog.FindBundleByName(bundleName);
            if (bundleEntry == null)
            {
                Debug.LogError($"[AssetBundleManager] Bundle '{bundleName}' not found in catalog!");
                onComplete?.Invoke(null);
                yield break;
            }

            // Load dependencies first
            if (bundleEntry.dependencies != null && bundleEntry.dependencies.Count > 0)
            {
                Debug.Log($"[AssetBundleManager] Loading {bundleEntry.dependencies.Count} dependencies for '{bundleName}'...");

                foreach (var depName in bundleEntry.dependencies)
                {
                    if (!_loadedBundles.ContainsKey(depName))
                    {
                        bool depLoaded = false;
                        yield return LoadBundleAsync(depName, (bundle) => { depLoaded = bundle != null; });

                        if (!depLoaded)
                        {
                            Debug.LogError($"[AssetBundleManager] Failed to load dependency '{depName}' for '{bundleName}'");
                            onComplete?.Invoke(null);
                            yield break;
                        }
                    }
                }
            }

            // Download and load the bundle WITH CACHING
            string bundleUrl = bundleEntry.GetFullUrl(_catalog.baseUrl);
            
            // Check if bundle is already cached
            bool isCached = Caching.IsVersionCached(bundleUrl, Hash128.Parse(bundleEntry.hash));
            if (isCached)
            {
                Debug.Log($"[AssetBundleManager] ✓ Bundle '{bundleName}' found in cache (INSTANT LOAD)");
            }
            else
            {
                Debug.Log($"[AssetBundleManager] Downloading bundle: {bundleUrl}");
                Debug.Log($"  Size: {bundleEntry.size / 1024f / 1024f:F2} MB");
            }

            AssetBundle downloadedBundle = null;
            string lastError = null;

            for (int attempt = 1; attempt <= maxRetries; attempt++)
            {
                if (Application.internetReachability == NetworkReachability.NotReachable)
                {
                    lastError = "No internet connection detected";
                    Debug.LogWarning($"[AssetBundleManager] Internet not reachable while downloading '{bundleName}'. Attempt {attempt}/{maxRetries}.");
                    yield return new WaitForSeconds(retryBackoffSeconds * attempt);
                    continue;
                }

                using (UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(bundleUrl, Hash128.Parse(bundleEntry.hash), 0))
                {
                    www.timeout = downloadTimeout;
                    www.disposeDownloadHandlerOnDispose = true;

                    var operation = www.SendWebRequest();
                    float lastProgress = -1f;
                    float stalledTimer = 0f;

                    // Report download progress (only shows progress if downloading)
                    while (!operation.isDone)
                    {
                        float progress = Mathf.Clamp01(www.downloadProgress);
                        if (!isCached)
                        {
                            OnBundleDownloadProgress?.Invoke(bundleName, progress);
                        }

                        if (!isCached && downloadStallTimeout > 0f)
                        {
                            if (Mathf.Approximately(progress, lastProgress))
                            {
                                stalledTimer += Time.unscaledDeltaTime;
                                if (stalledTimer >= downloadStallTimeout)
                                {
                                    Debug.LogWarning($"[AssetBundleManager] Download stalled for '{bundleName}' (progress {progress:P0}) after {stalledTimer:F1}s. Aborting attempt {attempt}/{maxRetries}.");
                                    www.Abort();
                                    break;
                                }
                            }
                            else
                            {
                                lastProgress = progress;
                                stalledTimer = 0f;
                            }
                        }

                        yield return null;
                    }

                    if (www.result == UnityWebRequest.Result.Success)
                    {
                        downloadedBundle = DownloadHandlerAssetBundle.GetContent(www);

                        if (downloadedBundle != null)
                        {
                            _loadedBundles[bundleName] = downloadedBundle;
                            
                            if (isCached)
                            {
                                Debug.Log($"[AssetBundleManager] ✓ Bundle '{bundleName}' loaded from cache!");
                            }
                            else
                            {
                                Debug.Log($"[AssetBundleManager] ✓ Bundle '{bundleName}' downloaded and cached!");
                                Debug.Log($"  Cached location: {Caching.defaultCache.path}");
                            }

                            OnBundleLoaded?.Invoke(bundleName, true);
                            onComplete?.Invoke(downloadedBundle);
                            yield break;
                        }

                        lastError = "Bundle extraction returned null";
                        Debug.LogError($"[AssetBundleManager] ✗ Failed to extract bundle from download: {bundleName}");
                    }
                    else
                    {
                        lastError = string.IsNullOrEmpty(www.error) ? www.result.ToString() : www.error;
                        Debug.LogWarning($"[AssetBundleManager] Attempt {attempt}/{maxRetries} failed for '{bundleName}': {lastError}");
                    }
                }

                if (attempt < maxRetries)
                {
                    float delay = retryBackoffSeconds * attempt;
                    Debug.Log($"[AssetBundleManager] Retrying '{bundleName}' in {delay:F1}s...");
                    yield return new WaitForSeconds(delay);
                }
            }

            Debug.LogError($"[AssetBundleManager] ✗ Failed to download bundle '{bundleName}': {lastError}");
            OnBundleLoaded?.Invoke(bundleName, false);
            onComplete?.Invoke(null);
            yield break;
        }

        /// <summary>
        /// Load a scene from a remote AssetBundle by scene name
        /// </summary>
        public IEnumerator LoadSceneAsync(string sceneName, Action<bool> onComplete = null)
        {
            if (!_isInitialized)
            {
                Debug.LogError("[AssetBundleManager] Not initialized! Call InitializeAsync() first.");
                onComplete?.Invoke(false);
                yield break;
            }

            // Find which bundle contains this scene
            var bundleEntry = _catalog.FindBundleByScene(sceneName);
            if (bundleEntry == null)
            {
                Debug.LogError($"[AssetBundleManager] Scene '{sceneName}' not found in any bundle!");
                onComplete?.Invoke(false);
                yield break;
            }

            Debug.Log($"[AssetBundleManager] Scene '{sceneName}' found in bundle '{bundleEntry.bundleName}'");
            var dependencyEntries = _catalog.GetAllDependencies(bundleEntry);

            if (runMemoryCleanupAroundSceneLoad)
            {
                yield return RunMemoryCleanupAsync($"[AssetBundleManager] Pre-load memory cleanup before '{sceneName}'");
            }

            // Load the bundle (and dependencies)
            bool bundleLoaded = false;
            yield return LoadBundleAsync(bundleEntry.bundleName, (bundle) => { bundleLoaded = bundle != null; });

            if (!bundleLoaded)
            {
                Debug.LogError($"[AssetBundleManager] Failed to load bundle for scene '{sceneName}'");
                onComplete?.Invoke(false);
                yield break;
            }

            // CRITICAL FIX: Load scene in SINGLE mode to avoid Unity bug with additive loading
            // The bug causes scene loading to hang at 0.2% when using Additive mode from AssetBundles
            Debug.Log($"[AssetBundleManager] Loading scene '{sceneName}' from bundle (SINGLE mode to avoid Unity hang bug)...");

            // Extra instrumentation: timestamp, loaded scenes, memory
            Debug.Log($"[AssetBundleManager] StartTime: {DateTime.UtcNow:O}");
            Debug.Log($"[AssetBundleManager] Current loaded scenes: {SceneManager.sceneCount}");
            for (int si = 0; si < SceneManager.sceneCount; si++)
            {
                var s = SceneManager.GetSceneAt(si);
                Debug.Log($"[AssetBundleManager] Scene[{si}] name={s.name} isLoaded={s.isLoaded}");
            }

            var sceneLoadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
            if (sceneLoadOperation == null)
            {
                Debug.LogError($"[AssetBundleManager] Failed to start scene load: {sceneName}");
                onComplete?.Invoke(false);
                yield break;
            }

            // Allow scene to activate immediately (no manual activation needed)
            sceneLoadOperation.allowSceneActivation = true;

            // Wait for scene to load with timeout protection and stall detection
            float timeout = 30f; // 30 second timeout
            float elapsed = 0f;

            // Track progress samples to detect stalls (common symptom: stuck at ~0.2)
            float lastProgress = -1f;
            float progressUnchangedTime = 0f;
            const float stallThresholdSeconds = 8f; // if progress unchanged for this long, treat as stall
            bool attemptedRetry = false;

            while (!sceneLoadOperation.isDone && elapsed < timeout)
            {
                float progress = sceneLoadOperation.progress; // 0..1

                // Log only when progress changes or at periodic intervals to avoid log spam
                if (Mathf.Approximately(progress, lastProgress))
                {
                    progressUnchangedTime += Time.deltaTime;
                }
                else
                {
                    lastProgress = progress;
                    progressUnchangedTime = 0f;
                    Debug.Log($"[AssetBundleManager] Scene load progress: {progress * 100:F1}% (elapsed {elapsed:F2}s)");
                }

                // If we detect a stall near 0.2 (historical hang), attempt a single retry
                if (!attemptedRetry && progress <= 0.21f && progressUnchangedTime > stallThresholdSeconds)
                {
                    Debug.LogWarning($"[AssetBundleManager] Detected scene-load stall at {progress * 100:F1}% for {progressUnchangedTime:F1}s. Attempting one retry...");

                    // Attempt to unload bundle and retry once after small delay
                    try
                    {
                        string retryBundle = bundleEntry.bundleName;
                        if (_loadedBundles.ContainsKey(retryBundle))
                        {
                            Debug.Log($"[AssetBundleManager] Unloading bundle '{retryBundle}' to attempt retry...");
                            UnloadBundle(retryBundle, true);
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.LogException(ex);
                    }

                    // wait a short moment and restart the load operation
                    yield return new WaitForSeconds(0.5f);

                    attemptedRetry = true;
                    elapsed = 0f;
                    lastProgress = -1f;
                    progressUnchangedTime = 0f;

                    Debug.Log("[AssetBundleManager] Starting retry of SceneManager.LoadSceneAsync...");
                    sceneLoadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
                    if (sceneLoadOperation == null)
                    {
                        Debug.LogError($"[AssetBundleManager] Retry failed to start scene load: {sceneName}");
                        onComplete?.Invoke(false);
                        yield break;
                    }
                    sceneLoadOperation.allowSceneActivation = true;
                }

                elapsed += Time.deltaTime;
                yield return null;
            }

            if (elapsed >= timeout && !sceneLoadOperation.isDone)
            {
                Debug.LogError($"[AssetBundleManager] Scene load timed out after {timeout} seconds! progress={sceneLoadOperation.progress * 100:F1}% attemptedRetry={attemptedRetry}");
                // dump some diagnostic info
                Debug.Log($"[AssetBundleManager] Loaded bundles: {string.Join(",", GetLoadedBundleNames())}");
                Debug.Log($"[AssetBundleManager] Memory stats: {GetMemoryStats()}");
                onComplete?.Invoke(false);
                yield break;
            }

            Debug.Log($"[AssetBundleManager] ✓ Scene '{sceneName}' loaded successfully in {elapsed:F2} seconds! attemptedRetry={attemptedRetry}");
            
            if (unloadSceneBundlesAfterLoad)
            {
                yield return null; // ensure the scene has a frame to settle before we release bundles
                ReleaseSceneBundles(bundleEntry, dependencyEntries);
            }

            if (runMemoryCleanupAroundSceneLoad)
            {
                yield return RunMemoryCleanupAsync($"[AssetBundleManager] Post-load memory cleanup after '{sceneName}'");
            }

            onComplete?.Invoke(true);
        }

        #endregion

        #region Cache Management

        /// <summary>
        /// Check if a bundle is cached on disk
        /// </summary>
        public bool IsBundleCached(string bundleName)
        {
            if (!_isInitialized || _catalog == null)
                return false;

            var bundleEntry = _catalog.FindBundleByName(bundleName);
            if (bundleEntry == null)
                return false;

            string bundleUrl = bundleEntry.GetFullUrl(_catalog.baseUrl);
            return Caching.IsVersionCached(bundleUrl, Hash128.Parse(bundleEntry.hash));
        }

        /// <summary>
        /// Get cache information
        /// </summary>
        public void GetCacheInfo()
        {
            Debug.Log($"[AssetBundleManager] Cache Info:");
            Debug.Log($"  Cache path: {Caching.defaultCache.path}");
            Debug.Log($"  Cache space used: {Caching.defaultCache.spaceOccupied / 1024f / 1024f:F2} MB");
            Debug.Log($"  Max cache size: {Caching.defaultCache.maximumAvailableStorageSpace / 1024f / 1024f / 1024f:F2} GB");
            Debug.Log($"  Caching ready: {Caching.ready}");
        }

        /// <summary>
        /// Clear all cached bundles (forces re-download on next load)
        /// </summary>
        public bool ClearCache()
        {
            bool success = Caching.ClearCache();
            if (success)
            {
                Debug.Log($"[AssetBundleManager] ✓ Cache cleared successfully!");
            }
            else
            {
                Debug.LogError($"[AssetBundleManager] ✗ Failed to clear cache");
            }
            return success;
        }

        /// <summary>
        /// Clear only old versions of cached bundles (keeps current version)
        /// </summary>
        public void CleanupOldCachedVersions()
        {
            bool success = Caching.ClearAllCachedVersions(Caching.defaultCache.path);
            if (success)
            {
                Debug.Log($"[AssetBundleManager] ✓ Cleaned up old cached versions");
            }
            else
            {
                Debug.LogWarning($"[AssetBundleManager] Failed to cleanup old cached versions");
            }
        }

        #endregion

        #region Memory Management

        /// <summary>
        /// Unload a specific bundle
        /// </summary>
        public void UnloadBundle(string bundleName, bool unloadAllLoadedObjects = false)
        {
            if (_loadedBundles.TryGetValue(bundleName, out AssetBundle bundle))
            {
                bundle.Unload(unloadAllLoadedObjects);
                _loadedBundles.Remove(bundleName);
                Debug.Log($"[AssetBundleManager] Unloaded bundle: {bundleName}");
            }
        }

        /// <summary>
        /// Unload all bundles
        /// </summary>
        public void UnloadAllBundles(bool unloadAllLoadedObjects = false)
        {
            foreach (var kvp in _loadedBundles)
            {
                kvp.Value.Unload(unloadAllLoadedObjects);
            }
            _loadedBundles.Clear();
            Debug.Log($"[AssetBundleManager] Unloaded all bundles");
        }

        /// <summary>
        /// Get memory usage stats
        /// </summary>
        public string GetMemoryStats()
        {
            long totalMemory = 0;
            foreach (var kvp in _loadedBundles)
            {
                // AssetBundle doesn't expose size directly, but we can estimate
                totalMemory += kvp.Value.GetAllAssetNames().Length * 1024; // Rough estimate
            }

            return $"Loaded Bundles: {_loadedBundles.Count}, Estimated Memory: {totalMemory / 1024f / 1024f:F2} MB";
        }

        private void ReleaseSceneBundles(RemoteAssetCatalog.BundleEntry rootBundle, List<RemoteAssetCatalog.BundleEntry> dependencyEntries)
        {
            if (rootBundle == null)
                return;

            var bundlesToRelease = new List<RemoteAssetCatalog.BundleEntry> { rootBundle };
            if (dependencyEntries != null)
            {
                bundlesToRelease.AddRange(dependencyEntries);
            }

            foreach (var entry in bundlesToRelease)
            {
                if (entry == null)
                    continue;

                if (_loadedBundles.ContainsKey(entry.bundleName))
                {
                    Debug.Log($"[AssetBundleManager] Releasing bundle '{entry.bundleName}' after scene load");
                    UnloadBundle(entry.bundleName, false);
                }
            }
        }

        private IEnumerator RunMemoryCleanupAsync(string reason)
        {
            Debug.Log(reason);

            // Give the UI a frame to update before we start releasing memory heavy objects.
            yield return null;

            yield return Resources.UnloadUnusedAssets();
            GC.Collect();

            if (logMemoryStats)
            {
                Debug.Log($"[AssetBundleManager] Runtime memory usage: {GetRuntimeMemoryStats()}");
            }
        }

        private string GetRuntimeMemoryStats()
        {
            long allocated = Profiler.GetTotalAllocatedMemoryLong();
            long reserved = Profiler.GetTotalReservedMemoryLong();
            long unused = Profiler.GetTotalUnusedReservedMemoryLong();

            return $"Allocated: {allocated / (1024f * 1024f):F1} MB | Reserved: {reserved / (1024f * 1024f):F1} MB | Unused Reserved: {unused / (1024f * 1024f):F1} MB | Bundles Cached: {GetMemoryStats()}";
        }

        #endregion

        #region Catalog Info

        /// <summary>
        /// Get the loaded catalog (null if not initialized)
        /// </summary>
        public RemoteAssetCatalog GetCatalog() => _catalog;

        /// <summary>
        /// Check if a bundle is loaded
        /// </summary>
        public bool IsBundleLoaded(string bundleName) => _loadedBundles.ContainsKey(bundleName);

        /// <summary>
        /// Get list of all loaded bundle names
        /// </summary>
        public List<string> GetLoadedBundleNames() => new List<string>(_loadedBundles.Keys);

        #endregion

        void OnDestroy()
        {
            UnloadAllBundles(true);
        }
    }
}
