using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using UnityEngine.AddressableAssets.ResourceLocators;

/// <summary>
/// Centralized scene loader that supports:
/// - Built-in scenes (always tries first)
/// - Remote Addressables (old system - fallback)
/// - Custom AssetBundles (new system - Editor: built-in, Android: remote bundles)
/// </summary>
public static class SceneLoader
{
    /// <summary>
    /// Wrapper abstraction that mimics AsyncOperation while backing either a SceneManager load or an Addressables handle.
    /// </summary>
    public sealed class SceneLoadHandle : CustomYieldInstruction
    {
        private readonly string _sceneName;
        private readonly Action<SceneLoadHandle> _onCompleted;
        private readonly LoadSceneMode _mode;

        private readonly AsyncOperation _unityAsyncOp;
        private AsyncOperationHandle<SceneInstance>? _addressableHandle;
        private AsyncOperation _activationOperation;

        private bool _allowSceneActivation;
        private bool _activationRequested;
        private bool _completed;

        // Constructor for pending initialization (no operation yet)
        internal SceneLoadHandle(string sceneName, LoadSceneMode mode, bool initialActivation, Action<SceneLoadHandle> onCompleted)
        {
            _sceneName = sceneName;
            _mode = mode;
            _allowSceneActivation = initialActivation;
            _onCompleted = onCompleted;
        }

        internal SceneLoadHandle(string sceneName, LoadSceneMode mode, AsyncOperation unityAsyncOp, bool initialActivation, Action<SceneLoadHandle> onCompleted)
        {
            _sceneName = sceneName;
            _mode = mode;
            _unityAsyncOp = unityAsyncOp;
            _onCompleted = onCompleted;

            if (_unityAsyncOp != null)
            {
                _unityAsyncOp.allowSceneActivation = initialActivation;
                _unityAsyncOp.completed += _ => Complete();
            }
            else
            {
                Debug.LogError($"[SceneLoader] Unity AsyncOperation is null for '{sceneName}'.");
                Complete();
            }
        }

        internal SceneLoadHandle(string sceneName, LoadSceneMode mode, AsyncOperationHandle<SceneInstance> addressableHandle, bool initialActivation, Action<SceneLoadHandle> onCompleted)
        {
            _sceneName = sceneName;
            _mode = mode;
            _addressableHandle = addressableHandle;
            _allowSceneActivation = initialActivation;
            _onCompleted = onCompleted;

            if (!addressableHandle.IsValid())
            {
                Debug.LogError($"[SceneLoader] Addressables handle is invalid for '{sceneName}'.");
                Complete();
                return;
            }

            addressableHandle.Completed += OnAddressableLoaded;

            if (initialActivation)
            {
                TryBeginActivation();
            }
        }

        // Update this handle with an Addressables operation
        internal void SetAddressableHandle(AsyncOperationHandle<SceneInstance> addressableHandle)
        {
            _addressableHandle = addressableHandle;

            if (!addressableHandle.IsValid())
            {
                Debug.LogError($"[SceneLoader] Addressables handle is invalid for '{_sceneName}'.");
                Complete();
                return;
            }

            addressableHandle.Completed += OnAddressableLoaded;

            if (_allowSceneActivation)
            {
                TryBeginActivation();
            }
        }

        public override bool keepWaiting => !isDone;

        public string SceneName => _sceneName;

        public bool allowSceneActivation
        {
            get
            {
                if (_unityAsyncOp != null)
                {
                    return _unityAsyncOp.allowSceneActivation;
                }

                return _allowSceneActivation;
            }
            set
            {
                if (_unityAsyncOp != null)
                {
                    _unityAsyncOp.allowSceneActivation = value;
                    return;
                }

                if (_allowSceneActivation == value)
                    return;

                _allowSceneActivation = value;
                if (value)
                {
                    TryBeginActivation();
                }
            }
        }

        public float progress
        {
            get
            {
                if (_unityAsyncOp != null)
                {
                    return _unityAsyncOp.progress;
                }

                if (!_addressableHandle.HasValue || !_addressableHandle.Value.IsValid())
                    return 0f;

                float baseProgress = _addressableHandle.Value.PercentComplete;

                if (_activationOperation != null)
                {
                    return Mathf.Lerp(Mathf.Min(baseProgress, 0.9f), 1f, _activationOperation.progress);
                }

                if (!_allowSceneActivation && baseProgress >= 0.9f)
                {
                    return 0.9f;
                }

                return baseProgress;
            }
        }

        public bool isDone
        {
            get
            {
                if (_unityAsyncOp != null)
                {
                    return _unityAsyncOp.isDone;
                }

                if (_activationOperation != null)
                {
                    return _activationOperation.isDone;
                }

                if (!_addressableHandle.HasValue || !_addressableHandle.Value.IsValid())
                {
                    return _completed;
                }

                if (!_allowSceneActivation)
                {
                    return false;
                }

                var handle = _addressableHandle.Value;
                if (handle.Status == AsyncOperationStatus.Failed)
                {
                    return true;
                }

                if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result.Scene.IsValid() && handle.Result.Scene.isLoaded)
                {
                    return true;
                }

                return false;
            }
        }

        public AsyncOperationHandle<SceneInstance>? AddressableHandle => _addressableHandle;

        private void OnAddressableLoaded(AsyncOperationHandle<SceneInstance> handle)
        {
            if (handle.Status == AsyncOperationStatus.Failed)
            {
                Debug.LogError($"[SceneLoader] Addressables failed to load '{_sceneName}': {handle.OperationException?.Message}");
                Complete();
                return;
            }

            if (_allowSceneActivation)
            {
                BeginActivation(handle);
            }
        }

        private void TryBeginActivation()
        {
            if (!_addressableHandle.HasValue || !_addressableHandle.Value.IsValid())
                return;

            if (_activationRequested)
                return;

            var handle = _addressableHandle.Value;
            if (handle.Status == AsyncOperationStatus.Succeeded)
            {
                BeginActivation(handle);
            }
        }

        private void BeginActivation(AsyncOperationHandle<SceneInstance> handle)
        {
            if (_activationRequested)
                return;

            _activationRequested = true;

            try
            {
                _activationOperation = handle.Result.ActivateAsync();
                if (_activationOperation != null)
                {
                    _activationOperation.completed += _ =>
                    {
                        if (_mode == LoadSceneMode.Single)
                        {
                            SceneManager.SetActiveScene(handle.Result.Scene);
                        }
                        Complete();
                    };
                }
                else
                {
                    if (_mode == LoadSceneMode.Single)
                    {
                        SceneManager.SetActiveScene(handle.Result.Scene);
                    }
                    Complete();
                }
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
                Complete();
            }
        }

        internal void Complete()
        {
            if (_completed)
                return;

            _completed = true;
            _onCompleted?.Invoke(this);
        }
    }

    private static SceneLoadHandle _currentHandle;
    private static string _currentSceneName;
    private static bool _addressablesInitialized;
    private static AsyncOperationHandle<IResourceLocator>? _initializationHandle;
    private static readonly object _addressablesInitLock = new();
    
    // Custom AssetBundle system (new system - replaces Addressables)
    private static bool _customAssetBundleInitialized;

    public static bool IsLoading => _currentHandle != null && !_currentHandle.isDone;
    public static string CurrentSceneName => _currentSceneName;
    public static SceneLoadHandle CurrentOperation => _currentHandle;

    /// <summary>
    /// Load a scene once, preferring build scenes but falling back to custom AssetBundles or Addressables.
    /// For "Game" scene: Uses built-in in Editor, remote AssetBundle in Android build.
    /// </summary>
    public static SceneLoadHandle LoadSceneAsyncOnce(
        string sceneName,
        LoadSceneMode mode = LoadSceneMode.Single,
        bool allowSceneActivation = true)
    {
        if (string.IsNullOrEmpty(sceneName))
        {
            Debug.LogError("SceneLoader: sceneName is null or empty.");
            return null;
        }

        if (IsLoading)
        {
            if (string.Equals(_currentSceneName, sceneName, StringComparison.Ordinal))
            {
                Debug.LogWarning($"SceneLoader: '{sceneName}' is already loading. Returning existing handle.");
                return _currentHandle;
            }

            Debug.LogWarning($"SceneLoader: A different scene ('{_currentSceneName}') is already loading. Ignoring new request '{sceneName}'.");
            return null;
        }

        _currentSceneName = sceneName;

// DISABLED: AssetBundle loading for Game scene
// Uncomment the block below to re-enable custom AssetBundle loading on Android
/*
#if !UNITY_EDITOR
        // CRITICAL FIX: On Android, ALWAYS use custom AssetBundle system for Game scene
        // Don't check Application.CanStreamedLevelBeLoaded - it's unreliable!
        if (sceneName == "Game")
        {
            Debug.Log("[SceneLoader] Android build detected - Loading Game scene via custom AssetBundle system...");
            var initHandle = new SceneLoadHandle(sceneName, mode, allowSceneActivation, OnHandleCompleted);
            _currentHandle = initHandle;
            
            if (GameManager.Instance != null)
            {
                GameManager.Instance.StartCoroutine(LoadGameSceneViaCustomAssetBundle(mode, allowSceneActivation, initHandle));
            }
            else
            {
                Debug.LogError("[SceneLoader] GameManager.Instance is null! Cannot load via custom AssetBundles.");
                ResetState();
                return null;
            }
            
            return initHandle;
        }
#endif
*/

        // 1. Try built-in scene first (works in Editor for development)
        if (Application.CanStreamedLevelBeLoaded(sceneName))
        {
            var asyncOp = SceneManager.LoadSceneAsync(sceneName, mode);
            if (asyncOp == null)
            {
                Debug.LogError($"SceneLoader: SceneManager returned null AsyncOperation for '{sceneName}'.");
                ResetState();
                return null;
            }

            asyncOp.allowSceneActivation = allowSceneActivation;
            _currentHandle = new SceneLoadHandle(sceneName, mode, asyncOp, allowSceneActivation, OnHandleCompleted);
            Debug.Log($"[SceneLoader] Started built-in async load for '{sceneName}'.");
            return _currentHandle;
        }

        // 2. For other scenes not in build: Use Addressables (fallback)
        Debug.Log($"[SceneLoader] Scene '{sceneName}' not in build settings.");
        
// DISABLED: Addressables fallback - make sure all scenes are in Build Settings
// Uncomment the block below to re-enable Addressables fallback loading
/*
#if !UNITY_EDITOR
        // This should not be reached for Game scene anymore (handled above)
        Debug.LogWarning($"[SceneLoader] Scene '{sceneName}' not found in build settings and not Game scene!");
#endif

        // 3. Fallback: Try Addressables for other scenes
        Debug.Log($"[SceneLoader] Attempting Addressables load for '{sceneName}'...");
        var addressablesHandle = new SceneLoadHandle(sceneName, mode, allowSceneActivation, OnHandleCompleted);
        _currentHandle = addressablesHandle;
        
        if (GameManager.Instance != null)
        {
            GameManager.Instance.StartCoroutine(InitializeAndLoadAddressableScene(sceneName, mode, allowSceneActivation, addressablesHandle));
        }
        else
        {
            Debug.LogError("[SceneLoader] GameManager.Instance is null! Cannot start coroutine for Addressables init.");
            ResetState();
            return null;
        }

        return addressablesHandle;
*/
        // Since AssetBundles/Addressables are disabled, log error and return null
        Debug.LogError($"[SceneLoader] Scene '{sceneName}' is NOT in Build Settings! Add it to Build Settings to load.");
        ResetState();
        return null;
    }

        private static void OnHandleCompleted(SceneLoadHandle handle)
        {
            if (handle != null)
            {
                var addrHandleNullable = handle.AddressableHandle;
                if (addrHandleNullable.HasValue)
                {
                    var addrHandle = addrHandleNullable.Value;
                    if (addrHandle.IsValid() && addrHandle.Status == AsyncOperationStatus.Failed)
                    {
                        Debug.LogError($"SceneLoader: Addressables load failed for '{handle.SceneName}'.");
                    }
                }
            }

            _currentHandle = null;
            _currentSceneName = null;
        }

    // Coroutine to load Game scene via custom AssetBundle system (Android builds only)
    private static IEnumerator LoadGameSceneViaCustomAssetBundle(
        LoadSceneMode mode,
        bool allowSceneActivation,
        SceneLoadHandle handle)
    {
        Debug.Log("[SceneLoader] Initializing custom AssetBundle system...");

        // Initialize custom AssetBundle system if not already done
        if (!_customAssetBundleInitialized)
        {
            yield return CustomAssetBundleSystem.RemoteAssetBundleManager.Instance.InitializeAsync();
            
            if (CustomAssetBundleSystem.RemoteAssetBundleManager.Instance.IsInitialized)
            {
                _customAssetBundleInitialized = true;
                Debug.Log("[SceneLoader] ✓ Custom AssetBundle system initialized!");
            }
            else
            {
                Debug.LogError("[SceneLoader] ✗ Failed to initialize custom AssetBundle system!");
                handle.Complete();
                yield break;
            }
        }

        // Load the Game scene from AssetBundle
        Debug.Log("[SceneLoader] Loading Game scene from remote AssetBundle...");
        bool sceneLoaded = false;
        
        yield return CustomAssetBundleSystem.RemoteAssetBundleManager.Instance.LoadSceneAsync("Game", (success) =>
        {
            sceneLoaded = success;
        });

        if (sceneLoaded)
        {
            Debug.Log("[SceneLoader] ✓ Game scene loaded successfully from AssetBundle!");
        }
        else
        {
            Debug.LogError("[SceneLoader] ✗ Failed to load Game scene from AssetBundle!");
        }

        // Complete the handle
        handle.Complete();
    }

    // Coroutine to handle async Addressables initialization without blocking main thread
    private static System.Collections.IEnumerator InitializeAndLoadAddressableScene(
        string sceneName,
        LoadSceneMode mode,
        bool allowSceneActivation,
        SceneLoadHandle handle)
    {
        Debug.Log($"[SceneLoader] Coroutine: Starting async Addressables init for '{sceneName}'...");

        // Start the async init task
        var initTask = EnsureAddressablesInitializedAsync();

        // Wait until the task completes without blocking
        while (!initTask.IsCompleted)
        {
            yield return null;
        }

        // Check result
        bool initSuccess = false;
        try
        {
            initSuccess = initTask.Result;
        }
        catch (Exception ex)
        {
            Debug.LogError($"[SceneLoader] Addressables initialization threw exception: {ex.Message}");
            Debug.LogException(ex);
        }

        if (!initSuccess)
        {
            Debug.LogError($"[SceneLoader] Addressables init failed. Cannot load '{sceneName}'.");
            ResetState();
            yield break;
        }

        Debug.Log("[SceneLoader] Addressables initialized. Locating scene address...");

        if (!TryLocateSceneAddress(sceneName, out var address))
        {
            Debug.LogError($"[SceneLoader] Scene '{sceneName}' not found in Addressables catalog.");
            ResetState();
            yield break;
        }

        Debug.Log($"[SceneLoader] Loading scene '{sceneName}' from address '{address}'...");
        var addressableHandle = Addressables.LoadSceneAsync(address, mode, activateOnLoad: false);
        handle.SetAddressableHandle(addressableHandle);
    }

    private static void ResetState()
    {
        _currentHandle = null;
        _currentSceneName = null;
    }

    internal static async System.Threading.Tasks.Task<bool> EnsureAddressablesInitializedAsync()
    {
        // Quick check without lock first
        if (_addressablesInitialized)
            return true;

        AsyncOperationHandle<IResourceLocator> handle;

        // Lock only for checking/starting initialization
        lock (_addressablesInitLock)
        {
            if (_addressablesInitialized)
                return true;

            if (!_initializationHandle.HasValue || !_initializationHandle.Value.IsValid())
            {
                _initializationHandle = Addressables.InitializeAsync();
            }

            handle = _initializationHandle.Value;

            if (!handle.IsValid())
            {
                Debug.LogError("[SceneLoader] Addressables initialization returned an invalid handle.");
                _initializationHandle = null;
                return false;
            }
        }

        // Await OUTSIDE the lock to avoid CS1996 error
        try
        {
            await handle.Task;

            // Capture status immediately after await, BEFORE any lock or release
            bool succeeded = false;
            AsyncOperationStatus status = AsyncOperationStatus.None;
            string operationException = null;
            
            if (handle.IsValid())
            {
                status = handle.Status;
                succeeded = status == AsyncOperationStatus.Succeeded;
                
                // Capture exception details if available
                if (handle.OperationException != null)
                {
                    operationException = handle.OperationException.ToString();
                }
            }

            // Lock again to update shared state and release
            lock (_addressablesInitLock)
            {
                if (handle.IsValid())
                {
                    Addressables.Release(handle);
                }

                _initializationHandle = null;

                if (succeeded)
                {
                    _addressablesInitialized = true;
                    Debug.Log("[SceneLoader] Addressables initialized successfully (async).");
                    return true;
                }

                // Log detailed failure information
                Debug.LogError($"[SceneLoader] Addressables initialization failed. Status: {status}");
                if (!string.IsNullOrEmpty(operationException))
                {
                    Debug.LogError($"[SceneLoader] Addressables OperationException: {operationException}");
                }
                else
                {
                    Debug.LogWarning("[SceneLoader] No OperationException available. Check catalog URL, network connectivity, and Addressables build settings.");
                }
                return false;
            }
        }
        catch (Exception ex)
        {
            lock (_addressablesInitLock)
            {
                if (_initializationHandle.HasValue && _initializationHandle.Value.IsValid())
                {
                    Addressables.Release(_initializationHandle.Value);
                }

                _initializationHandle = null;
            }
            Debug.LogError($"[SceneLoader] Addressables async init exception: {ex.GetType().Name} - {ex.Message}");
            Debug.LogError($"[SceneLoader] Full exception details:\n{ex.ToString()}");
            Debug.LogException(ex);
            return false;
        }
    }

    private static bool TryLocateSceneAddress(string sceneName, out string address)
    {
        address = null;

        foreach (var candidate in EnumerateAddressCandidates(sceneName))
        {
            foreach (var locator in Addressables.ResourceLocators)
            {
                if (locator.Locate(candidate, typeof(SceneInstance), out IList<IResourceLocation> locations) && locations != null && locations.Count > 0)
                {
                    address = candidate;
                    return true;
                }
            }
        }

        return false;
    }

    private static IEnumerable<string> EnumerateAddressCandidates(string sceneName)
    {
        if (string.IsNullOrWhiteSpace(sceneName)) yield break;

        yield return sceneName;

        if (!sceneName.EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
        {
            yield return sceneName + ".unity";
        }

        yield return $"Assets/Scenes/{sceneName}.unity";
        yield return $"Assets/{sceneName}.unity";
    }
}
