using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using GeoSus.Client;

namespace Subsystems
{
    /// <summary>
    /// Round-robin task-to-minigame assignment, proximity detection, additive scene launch.
    /// </summary>
    public class GameManager_Tasks
    {
        private class TaskEntry
        {
            public GeoSus.Client.GameTask ServerTask;
            public string MinigameScene;
            public bool Completed;
        }

        private GameClient _gameClient;
        private string[] _minigameScenes;
        private MonoBehaviour _host; // GameManager MonoBehaviour for coroutines
        private List<TaskEntry> _tasks = new List<TaskEntry>();
        private bool _minigameOpen;
        private string _loadedMinigameScene;
        private Camera _hostCameraSuspended;
        private GameObject _hostInGameHudHidden;

        // Proximity state (checked every frame in UpdateProximity)
        public GeoSus.Client.GameTask NearbyTask { get; private set; }

        // P13b: per-check distances pulled from the server-snapshotted lobby
        // settings (null-fallback to 5m matches the old hardcoded behavior).
        // Different actions use different fields so a host can tune e.g. a
        // long-range "spotter" task radius without also widening kill range.
        private const float ProximityRadiusFallback = 5f;

        public GameManager_Tasks(GameClient gameClient, string[] minigameScenes, MonoBehaviour host)
        {
            _gameClient = gameClient;
            _minigameScenes = minigameScenes ?? new string[0];
            _host = host;
        }

        /// <summary>Called by Network subsystem when RoleAssigned fires.</summary>
        public void Initialize(List<GeoSus.Client.GameTask> serverTasks)
        {
            _tasks.Clear();
            if (_minigameScenes.Length == 0) return;

            for (int i = 0; i < serverTasks.Count; i++)
            {
                _tasks.Add(new TaskEntry
                {
                    ServerTask  = serverTasks[i],
                    MinigameScene = _minigameScenes[i % _minigameScenes.Length],
                    Completed   = false
                });
            }

            // Create map markers
            GameManager.Instance?.mapSubsystem?.CreateTaskMarkers(serverTasks);
            Debug.Log($"[Tasks] Initialized {_tasks.Count} tasks.");
        }

        /// <summary>Called every frame from GameManager.Update().</summary>
        public void UpdateProximity()
        {
            if (_minigameOpen) return;

            // P13b: distances now come from the per-lobby settings snapshot
            // instead of one hardcoded 5m radius for everything. ?? fallback
            // matches the old behavior when running against an old server.
            var state = GameManager.Instance?.networkSubsystem?.State;
            var settings = state?.Settings;
            double taskDist      = settings?.TaskStartDistanceM         ?? ProximityRadiusFallback;
            double reportDist    = settings?.ReportDistanceM            ?? ProximityRadiusFallback;
            double emergencyDist = settings?.EmergencyMeetingCallRadiusM?? ProximityRadiusFallback;
            double killDist      = settings?.KillDistanceM              ?? ProximityRadiusFallback;

            NearbyTask = null;
            var myPos = _gameClient.MyPosition;
            if (myPos.Lat == 0 && myPos.Lon == 0) return;

            foreach (var entry in _tasks)
            {
                if (entry.Completed) continue;
                double dist = myPos.DistanceTo(entry.ServerTask.Location);
                if (dist <= taskDist)
                {
                    NearbyTask = entry.ServerTask;
                    break;
                }
            }

            // Drive the action button in UI
            var ui = GameManager.Instance?.uiSubsystem;
            if (ui == null || ui.IsPlayerDead) return;

            bool isImpostor = _gameClient.MyRole == GeoSus.Client.PlayerRole.Impostor;

            if (!isImpostor && NearbyTask != null)
            {
                ui.SetActionButton("USE", true, () => GameManager.Instance?.PerformAction());
                return;
            }

            // Check body proximity
            if (!ui.IsCommsBlackout)
            {
                var body = _gameClient.FindNearbyBody(reportDist);
                if (body != null)
                {
                    ui.SetActionButton("REPORT", true, () => GameManager.Instance?.PerformAction());
                    return;
                }

                // Emergency meeting proximity
                if (_gameClient.CurrentLobbyState?.MapData != null)
                {
                    double dist = myPos.DistanceTo(_gameClient.CurrentLobbyState.MapData.Center);
                    if (dist <= emergencyDist)
                    {
                        ui.SetActionButton("EMERGENCY", true, () => GameManager.Instance?.PerformAction());
                        return;
                    }
                }
            }

            // Impostor kill
            if (isImpostor)
            {
                var target = _gameClient.FindNearbyPlayer(killDist);
                if (!string.IsNullOrEmpty(target))
                {
                    ui.SetActionButton("KILL", true, () => GameManager.Instance?.PerformAction());
                    // Hide sabotage menu while a kill is on offer (cleaner HUD).
                    ui.SetSabotageMenuVisible(false);
                    return;
                }
            }

            // Nothing nearby
            ui.SetActionButton("", false);

            // P13g: persistent sabotage menu for impostors when no proximity
            // action is on offer. Hidden when state isn't suitable - dead,
            // not-impostor, in meeting, sabotage already active, or comms
            // blackout (the impostor's own sabotage triggers a UI lock).
            bool inPlayingPhase = state != null && state.Phase == GeoSus.Client.GamePhase.Playing;
            bool sabotageActive = state?.ActiveSabotage != null;
            bool showSabMenu = isImpostor && !ui.IsPlayerDead && inPlayingPhase &&
                               !sabotageActive && !ui.IsCommsBlackout;
            ui.SetSabotageMenuVisible(showSabMenu);
        }

        /// <summary>Called externally (e.g., GameManager.PerformAction) to launch the nearby task.</summary>
        public void TriggerNearbyTask()
        {
            OnUsePressed();
        }

        private void OnUsePressed()
        {
            if (NearbyTask == null || _minigameOpen) return;
            var entry = _tasks.Find(t => t.ServerTask.TaskId == NearbyTask.TaskId);
            if (entry != null) _host.StartCoroutine(LaunchMinigame(entry));
        }

        private IEnumerator LaunchMinigame(TaskEntry entry)
        {
            _minigameOpen = true;
            Debug.Log($"[Tasks] Launching minigame '{entry.MinigameScene}' for task '{entry.ServerTask.Name}'");

            // Validate that the scene name resolves to a build-included scene.
            // LoadSceneAsync silently returns null when the scene name doesn't
            // match (case-sensitive) or isn't in EditorBuildSettings, which
            // leaves the action button looking dead from the player's POV.
            if (string.IsNullOrEmpty(entry.MinigameScene) ||
                !Application.CanStreamedLevelBeLoaded(entry.MinigameScene))
            {
                Debug.LogError($"[Tasks] Minigame scene '{entry.MinigameScene}' is not loadable. " +
                               $"Check the scene name (case-sensitive) and that it's enabled in Build Settings.");
                GameManager.Instance?.uiSubsystem?.ShowToast(
                    $"Task scene missing: {entry.MinigameScene}");
                _minigameOpen = false;
                yield break;
            }

            // Inform server that task started
            _gameClient.Send(new TaskStart { TaskId = entry.ServerTask.TaskId });

            // Disable the host scene's main camera while the minigame is up.
            // With both cameras enabled the minigame's UI/3D content would
            // fight the host's map camera for screen space, and what gets
            // drawn depends on Camera.depth which isn't guaranteed across
            // scenes. Restored in FinishMinigame.
            _hostCameraSuspended = Camera.main;
            if (_hostCameraSuspended != null) _hostCameraSuspended.enabled = false;

            // Hide the persistent InGame HUD canvas (if present). It lives
            // in Client.unity and renders Screen Space - Overlay so it would
            // otherwise stack on top of the minigame's UI regardless of
            // which scene is active. SetActive(false) is reversible.
            _hostInGameHudHidden = GameObject.Find("InGame");
            if (_hostInGameHudHidden != null && _hostInGameHudHidden.activeSelf)
                _hostInGameHudHidden.SetActive(false);
            else
                _hostInGameHudHidden = null; // nothing to restore

            var op = SceneManager.LoadSceneAsync(entry.MinigameScene, LoadSceneMode.Additive);
            if (op == null)
            {
                Debug.LogError($"[Tasks] LoadSceneAsync returned null for '{entry.MinigameScene}'.");
                GameManager.Instance?.uiSubsystem?.ShowToast(
                    $"Task scene failed to load: {entry.MinigameScene}");
                if (_hostCameraSuspended != null) { _hostCameraSuspended.enabled = true; _hostCameraSuspended = null; }
                _minigameOpen = false;
                yield break;
            }
            yield return op;

            _loadedMinigameScene = entry.MinigameScene;

            // CRITICAL: switch the active scene to the loaded minigame.
            // LoadSceneMode.Additive stacks scenes without changing which one
            // is "active" - and an inactive scene's RenderSettings, ambient
            // light, and skybox don't drive rendering. The host (Client.unity)
            // remains active and its lighting context still applies, which
            // is the root cause of "task opens to white screen": the
            // minigame's content loads but its visuals don't take over.
            // Without SetActiveScene, even minigames that ARE wired up
            // correctly render against the host's lighting and look broken.
            Scene scene = SceneManager.GetSceneByName(entry.MinigameScene);
            if (scene.IsValid()) SceneManager.SetActiveScene(scene);

            // Diagnostic: count cameras / canvases / lights in the loaded
            // scene. If the white screen persists after this fix, the
            // numbers tell us whether the scene is missing rendering bits
            // (camera=0, canvas=0) or if the issue is elsewhere.
            int camCount = 0, canvasCount = 0, lightCount = 0;
            foreach (var root in scene.GetRootGameObjects())
            {
                camCount    += root.GetComponentsInChildren<Camera>(true).Length;
                canvasCount += root.GetComponentsInChildren<Canvas>(true).Length;
                lightCount  += root.GetComponentsInChildren<Light>(true).Length;
            }
            Debug.Log($"[Tasks] Loaded '{entry.MinigameScene}': cameras={camCount}, " +
                      $"canvases={canvasCount}, lights={lightCount}, " +
                      $"activeScene={SceneManager.GetActiveScene().name}");

            // Find the ITask component in the newly loaded scene
            ITask taskComponent = null;
            foreach (var root in scene.GetRootGameObjects())
            {
                taskComponent = root.GetComponentInChildren<ITask>();
                if (taskComponent != null) break;
            }

            if (taskComponent == null)
            {
                Debug.LogWarning($"[Tasks] No ITask found in '{entry.MinigameScene}'. " +
                                 $"Either the minigame's controller script isn't attached to a GameObject in the scene, " +
                                 $"or the script doesn't implement ITask. Auto-completing.");
                yield return FinishMinigame(entry, true);
                yield break;
            }

            // Set task metadata
            taskComponent.TaskID = entry.ServerTask.TaskId;
            taskComponent.TaskName = entry.ServerTask.Name;
            taskComponent.TaskLocation = (entry.ServerTask.Location.Lat, entry.ServerTask.Location.Lon);

            bool done = false;
            taskComponent.Initialize(t => { done = true; });

            // Wait for completion or exit
            yield return new WaitUntil(() => done);

            yield return FinishMinigame(entry, done);
        }

        private IEnumerator FinishMinigame(TaskEntry entry, bool completed)
        {
            if (completed)
            {
                entry.Completed = true;
                _gameClient.CompleteTask(entry.ServerTask.TaskId);
                Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' completed.");
            }
            else
            {
                Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' exited without completion.");
            }

            // Unload minigame scene. Switch the active scene back to the
            // host BEFORE the unload so we don't end up with no active
            // scene mid-frame (Unity will complain and lighting flickers).
            if (!string.IsNullOrEmpty(_loadedMinigameScene))
            {
                var hostScene = SceneManager.GetSceneByName("Client");
                if (hostScene.IsValid()) SceneManager.SetActiveScene(hostScene);

                var unload = SceneManager.UnloadSceneAsync(_loadedMinigameScene);
                yield return unload;
                _loadedMinigameScene = null;
            }

            // Re-enable the host camera that was suspended during the minigame.
            if (_hostCameraSuspended != null)
            {
                _hostCameraSuspended.enabled = true;
                _hostCameraSuspended = null;
            }

            // Re-show the InGame HUD canvas hidden at minigame entry.
            if (_hostInGameHudHidden != null)
            {
                _hostInGameHudHidden.SetActive(true);
                _hostInGameHudHidden = null;
            }

            _minigameOpen = false;
        }
    }
}
