using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using KSP.UI.Screens;
using UnityEngine;

namespace JebAI
{
    /// <summary>
    /// Primary KSP addon for the prototype.
    ///
    /// Responsibilities:
    ///   1. Spawns persistent UI managers (speech bubbles and chat window).
    ///   2. Exposes all player-triggered interaction methods for Kerbal-focused UI.
    ///   3. Attaches interaction modules to loaded crewed parts and EVA Kerbals.
    ///   4. Maintains mood decay and a low-fuel proactive reaction.
    /// </summary>
    [KSPAddon(KSPAddon.Startup.Flight, false)]
    public class JebAI : MonoBehaviour
    {
        public static JebAI Instance { get; private set; }

        private bool _lowFuelWarningFired = false;
        private float _lastFuelCheckTime = 0f;
        private bool _windowVisible = false;
        private Rect _windowRect = new Rect(70f, 110f, 420f, 320f);
        private Vector2 _crewScroll = Vector2.zero;
        private ApplicationLauncherButton _toolbarButton;
        private Texture2D _toolbarIcon;

        private const float FuelCheckInterval = 5f;
        private const float LowFuelThreshold = 10.0f;
        private const float MoodDecayInterval = 3f;
        private float _lastMoodDecayTime = 0f;
        private const int WindowId = 0x4A454241;

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

        void Start()
        {
            JebAISettings.Reload();
            JebAIPromptConfig.Reload();
            KerbalMemory.EnsureLoaded();

            SpawnManager<JebAIMcpLayer>("JebAI_MCP");
            SpawnManager<SpeechBubbleManager>("JebAI_Bubbles");
            SpawnManager<ChatWindow>("JebAI_Chat");
            GameEvents.onGUIApplicationLauncherReady.Add(OnAppLauncherReady);
            GameEvents.onGUIApplicationLauncherDestroyed.Add(OnAppLauncherDestroyed);
            GameEvents.onFlightReady.Add(OnFlightReady);
            GameEvents.onVesselChange.Add(OnVesselChange);
            GameEvents.onVesselWasModified.Add(OnVesselWasModified);
            GameEvents.onCrewOnEva.Add(OnCrewOnEva);
            TryCreateToolbarButton();
            StartCoroutine(InstallInteractionModulesNextFrame());

            Debug.Log("[JebAI] Flight addon started.");
        }

        void Update()
        {
            if (Time.time - _lastMoodDecayTime > MoodDecayInterval)
            {
                _lastMoodDecayTime = Time.time;
                KerbalMoodTracker.DecayAll(MoodDecayInterval);
            }

            if (!_lowFuelWarningFired && FlightGlobals.ActiveVessel != null)
                CheckLowFuel();
        }

        void OnGUI()
        {
            if (!_windowVisible) return;
            _windowRect = GUILayout.Window(WindowId, _windowRect, DrawWindow, "JebAI");
        }

        void OnDestroy()
        {
            GameEvents.onGUIApplicationLauncherReady.Remove(OnAppLauncherReady);
            GameEvents.onGUIApplicationLauncherDestroyed.Remove(OnAppLauncherDestroyed);
            GameEvents.onFlightReady.Remove(OnFlightReady);
            GameEvents.onVesselChange.Remove(OnVesselChange);
            GameEvents.onVesselWasModified.Remove(OnVesselWasModified);
            GameEvents.onCrewOnEva.Remove(OnCrewOnEva);
            RemoveToolbarButton();

            if (Instance == this)
                Instance = null;

            Debug.Log("[JebAI] Flight addon destroyed.");
        }

        public void OpenChat(ProtoCrewMember kerbal, Part sourcePart = null)
        {
            PrepareKerbalForInteraction(kerbal, ResolveContextVessel(sourcePart));
            ChatWindow.Instance?.OpenFor(kerbal);
        }

        public void TriggerPoke(ProtoCrewMember kerbal, Part sourcePart = null)
        {
            Vessel contextVessel = ResolveContextVessel(sourcePart);
            PrepareKerbalForInteraction(kerbal, contextVessel);
            string ctx = ContextBuilder.BuildFlightContext(contextVessel);
            string system = KerbalPersona.BuildSystemPrompt(kerbal, InteractionType.Poke);
            string prompt = $"The player just poked you. Use these flight notes only as background; do not paraphrase them back.\n{ctx}";
            RequestInteraction(
                kerbal,
                sourcePart,
                system,
                prompt,
                JebAISettings.Instance.MaxTokensPoke,
                InteractionType.Poke,
                "Poke");
        }

        public void TriggerSpeak(ProtoCrewMember kerbal, Part sourcePart = null)
        {
            Vessel contextVessel = ResolveContextVessel(sourcePart);
            PrepareKerbalForInteraction(kerbal, contextVessel);
            string ctx = ContextBuilder.BuildFlightContext(contextVessel);
            string system = KerbalPersona.BuildSystemPrompt(kerbal, InteractionType.Speak);
            string prompt = $"The player asked you to say something. Use these flight notes only as background; do not turn them into a report.\n{ctx}";
            RequestInteraction(
                kerbal,
                sourcePart,
                system,
                prompt,
                JebAISettings.Instance.MaxTokensSpeak,
                InteractionType.Speak,
                "Speak");
        }

        public void TriggerSpecial(
            ProtoCrewMember kerbal,
            InteractionType type,
            Part sourcePart = null)
        {
            Vessel contextVessel = ResolveContextVessel(sourcePart);
            PrepareKerbalForInteraction(kerbal, contextVessel);
            string ctx = ContextBuilder.BuildFlightContext(contextVessel);
            string system = KerbalPersona.BuildSystemPrompt(kerbal, type);

            string prompt = type switch
            {
                InteractionType.AskFears => $"The player asked what you're actually afraid of. Use these flight notes only as background; do not summarize them.\n{ctx}",
                InteractionType.AskDreams => $"The player wants to know your real goal - what you're flying toward. Use these flight notes only as background; do not summarize them.\n{ctx}",
                InteractionType.AskMission => $"The player asked you to reflect on this mission. Use these flight notes only as background; do not recap them line by line.\n{ctx}",
                _ => $"The player asked you something personal. Use these flight notes only as background; do not summarize them.\n{ctx}",
            };

            RequestInteraction(
                kerbal,
                sourcePart,
                system,
                prompt,
                JebAISettings.Instance.MaxTokensSpeak,
                type,
                type.ToString());
        }

        public void TriggerAskAboutCrewmate(
            ProtoCrewMember kerbal,
            string crewmateName,
            Part sourcePart = null)
        {
            Vessel contextVessel = ResolveContextVessel(sourcePart);
            PrepareKerbalForInteraction(kerbal, contextVessel);
            string ctx = ContextBuilder.BuildFlightContext(contextVessel);
            string system = KerbalPersona.BuildSystemPrompt(
                kerbal,
                InteractionType.AskCrewmate,
                targetCrewmate: crewmateName);

            string prompt =
                $"The player is asking how you honestly feel about {crewmateName}.\n" +
                $"You're both on this vessel right now. Use these flight notes only as background; do not summarize them.\n{ctx}";

            RequestInteraction(
                kerbal,
                sourcePart,
                system,
                prompt,
                JebAISettings.Instance.MaxTokensSpeak,
                InteractionType.AskCrewmate,
                $"AskCrewmate:{crewmateName}");
        }

        public void PrepareKerbalForInteraction(ProtoCrewMember kerbal, Vessel contextVessel = null)
        {
            if (kerbal == null) return;

            KerbalMoodTracker.ApplyAmbientContext(
                kerbal.name,
                contextVessel ?? FlightGlobals.ActiveVessel,
                kerbal.courage,
                kerbal.stupidity);
        }

        private void CheckLowFuel()
        {
            if (Time.time - _lastFuelCheckTime < FuelCheckInterval) return;
            _lastFuelCheckTime = Time.time;

            Vessel v = FlightGlobals.ActiveVessel;
            if (v == null) return;

            double lfA = 0d;
            double lfM = 0d;
            foreach (Part p in v.parts)
                foreach (PartResource r in p.Resources)
                    if (r.resourceName == "LiquidFuel") { lfA += r.amount; lfM += r.maxAmount; }

            if (lfM <= 0d) return;

            double pct = (lfA / lfM) * 100.0;
            if (pct < LowFuelThreshold && pct > 0.1)
            {
                _lowFuelWarningFired = true;
                EventListener.Instance?.TriggerCrewReaction(GameEvent.LowFuel);
            }
        }

        private void HandleReply(
            ProtoCrewMember kerbal,
            Part sourcePart,
            string reply,
            InteractionType interactionType,
            string interactionLabel)
        {
            string compactReply = OpenAICompatClient.ClampReplyForInteraction(reply, interactionType);
            if (!string.IsNullOrWhiteSpace(compactReply))
            {
                SpeechBubbleManager.Instance?.Show(kerbal, compactReply, sourcePart);
                KerbalMemory.AddRecentOutput(kerbal.name, interactionType, compactReply);
                Debug.Log($"[JebAI] [{interactionLabel}] {kerbal.name}: {compactReply}");
            }
            else
            {
                string server = JebAISettings.Instance.GetServerDisplayName();
                Debug.LogWarning($"[JebAI] No response for {kerbal.name} on {interactionLabel}. Is {server} running?");
                ScreenMessages.PostScreenMessage(
                    $"[JebAI] No response - is {server} running?",
                    4f,
                    ScreenMessageStyle.UPPER_CENTER);
            }
        }

        private void RequestInteraction(
            ProtoCrewMember kerbal,
            Part sourcePart,
            string systemPrompt,
            string userPrompt,
            int maxTokens,
            InteractionType interactionType,
            string interactionLabel)
        {
            if (kerbal == null) return;

            if (JebAIMcpLayer.Instance == null)
            {
                HandleFailure(kerbal, interactionLabel, "MCP layer is unavailable.");
                return;
            }

            JebAIMcpLayer.Instance.RequestCompletion(
                systemPrompt,
                null,
                userPrompt,
                maxTokens,
                $"{interactionLabel}:{kerbal.name}",
                result =>
                {
                    if (result != null && result.Success)
                    {
                        HandleReply(kerbal, sourcePart, result.Reply, interactionType, interactionLabel);
                        return;
                    }

                    if (ShouldUseSyntheticFallback(result?.Error))
                    {
                        string synthetic = BuildSyntheticFallback(kerbal, interactionType);
                        HandleReply(kerbal, sourcePart, synthetic, interactionType, interactionLabel + ":Fallback");
                        return;
                    }

                    HandleFailure(kerbal, interactionLabel, result?.Error);
                });
        }

        private static T SpawnManager<T>(string name) where T : UnityEngine.MonoBehaviour
        {
            var existing = FindObjectOfType<T>();
            if (existing != null) return existing;

            var go = new GameObject(name);
            DontDestroyOnLoad(go);
            return go.AddComponent<T>();
        }

        private static Vessel ResolveContextVessel(Part sourcePart)
        {
            return sourcePart?.vessel ?? FlightGlobals.ActiveVessel;
        }

        private static void HandleFailure(
            ProtoCrewMember kerbal,
            string interactionLabel,
            string error)
        {
            string detail = string.IsNullOrWhiteSpace(error)
                ? "No response - check MCP status."
                : error;

            Debug.LogWarning($"[JebAI] {interactionLabel} failed for {kerbal?.name}: {detail}");
            ScreenMessages.PostScreenMessage(
                $"[JebAI] {interactionLabel} failed: {detail}",
                4f,
                ScreenMessageStyle.UPPER_CENTER);
        }

        private static bool ShouldUseSyntheticFallback(string error)
        {
            if (string.IsNullOrWhiteSpace(error)) return false;

            string lower = error.ToLowerInvariant();
            return lower.Contains("reasoning content")
                || lower.Contains("empty choices")
                || lower.Contains("blank message content")
                || lower.Contains("parse failed");
        }

        private static string BuildSyntheticFallback(ProtoCrewMember kerbal, InteractionType type)
        {
            switch (type)
            {
                case InteractionType.Poke:
                case InteractionType.StagingReaction:
                    return "Hey, helmet taps received.";
                case InteractionType.Speak:
                    return "I'm here, focused, and still flying.";
                case InteractionType.Chat:
                    return "Comms glitched for a second, but I'm here.";
                case InteractionType.EventReaction:
                    return "By the Kraken, that was too close.";
                case InteractionType.AskFears:
                    return "Quiet truth? I fear the slow, boring failures most.";
                case InteractionType.AskDreams:
                    return "I want the mission that changes everything.";
                case InteractionType.AskMission:
                    return "It's tense, but it still feels worth it.";
                case InteractionType.AskCrewmate:
                    return "I have opinions, sure, but I'd still fly with this crew.";
                default:
                    return "Radio crackled, but I'm still with you.";
            }
        }

        private void DrawWindow(int id)
        {
            Vessel vessel = FlightGlobals.ActiveVessel;
            GUILayout.BeginVertical();

            if (vessel == null)
            {
                GUILayout.Label("No active vessel.");
            }
            else
            {
                GUILayout.Label($"Vessel: {vessel.vesselName}");
                GUILayout.Label($"MCP: {JebAIMcpLayer.Instance?.DescribeStatus() ?? "Unavailable"}");

                GUILayout.BeginHorizontal();
                if (GUILayout.Button("Reload Config"))
                {
                    JebAISettings.Reload();
                    JebAIPromptConfig.Reload();
                }
                if (GUILayout.Button("Ping Server"))
                    JebAIMcpLayer.Instance?.ProbeServer();
                GUILayout.EndHorizontal();

                List<ProtoCrewMember> crew = vessel.GetVesselCrew();
                if (crew == null || crew.Count == 0)
                {
                    GUILayout.Label("No Kerbals on the active vessel.");
                }
                else
                {
                    _crewScroll = GUILayout.BeginScrollView(_crewScroll, GUILayout.Height(240f));
                    foreach (ProtoCrewMember kerbal in crew)
                        DrawCrewRow(kerbal, vessel);
                    GUILayout.EndScrollView();
                }
            }

            GUILayout.EndVertical();
            GUI.DragWindow(new Rect(0f, 0f, 10000f, 24f));
        }

        private void DrawCrewRow(ProtoCrewMember kerbal, Vessel vessel)
        {
            string mood = KerbalMoodTracker.Get(kerbal.name).Describe();
            Part sourcePart = FindCrewPart(kerbal.name, vessel);

            GUILayout.BeginVertical(GUI.skin.box);
            GUILayout.Label($"{kerbal.name} [{kerbal.trait}]");
            GUILayout.Label(mood);

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Poke")) TriggerPoke(kerbal, sourcePart);
            if (GUILayout.Button("Speak")) TriggerSpeak(kerbal, sourcePart);
            if (GUILayout.Button("Chat")) OpenChat(kerbal, sourcePart);
            GUILayout.EndHorizontal();
            GUILayout.EndVertical();
        }

        private void OnAppLauncherReady()
        {
            TryCreateToolbarButton();
        }

        private void OnAppLauncherDestroyed()
        {
            RemoveToolbarButton();
        }

        private void TryCreateToolbarButton()
        {
            if (_toolbarButton != null || ApplicationLauncher.Instance == null)
                return;

            if (_toolbarIcon == null)
                _toolbarIcon = BuildToolbarIcon();

            _toolbarButton = ApplicationLauncher.Instance.AddModApplication(
                OnToolbarEnabled,
                OnToolbarDisabled,
                null,
                null,
                null,
                null,
                ApplicationLauncher.AppScenes.FLIGHT,
                _toolbarIcon);
        }

        private void RemoveToolbarButton()
        {
            if (_toolbarButton != null && ApplicationLauncher.Instance != null)
                ApplicationLauncher.Instance.RemoveModApplication(_toolbarButton);

            _toolbarButton = null;
        }

        private void OnToolbarEnabled() { _windowVisible = true; }
        private void OnToolbarDisabled() { _windowVisible = false; }

        private static Texture2D BuildToolbarIcon()
        {
            var tex = new Texture2D(38, 38, TextureFormat.RGBA32, false);
            Color background = new Color(0.08f, 0.13f, 0.20f, 1f);
            Color accent = new Color(0.48f, 0.88f, 0.45f, 1f);
            Color highlight = new Color(0.82f, 0.96f, 0.92f, 1f);

            for (int y = 0; y < tex.height; y++)
            {
                for (int x = 0; x < tex.width; x++)
                {
                    bool border = x < 2 || y < 2 || x >= tex.width - 2 || y >= tex.height - 2;
                    bool stripe = x > 8 && x < 14 && y > 8 && y < 30;
                    bool bar = y > 24 && y < 30 && x > 8 && x < 28;
                    tex.SetPixel(x, y, border ? accent : (stripe || bar ? highlight : background));
                }
            }

            tex.Apply();
            return tex;
        }

        private static Part FindCrewPart(string kerbalName, Vessel vessel)
        {
            if (vessel?.parts == null) return null;

            foreach (Part part in vessel.parts)
            {
                if (part?.protoModuleCrew == null) continue;
                foreach (ProtoCrewMember crew in part.protoModuleCrew)
                    if (crew != null && crew.name == kerbalName)
                        return part;
            }

            return vessel.rootPart;
        }

        private void OnFlightReady()
        {
            StartCoroutine(InstallInteractionModulesNextFrame());
        }

        private void OnVesselChange(Vessel vessel)
        {
            InstallInteractionModules(vessel);
        }

        private void OnVesselWasModified(Vessel vessel)
        {
            InstallInteractionModules(vessel);
        }

        private void OnCrewOnEva(GameEvents.FromToAction<Part, Part> _)
        {
            StartCoroutine(InstallInteractionModulesNextFrame());
        }

        private IEnumerator InstallInteractionModulesNextFrame()
        {
            yield return null;
            InstallInteractionModules();
        }

        private void InstallInteractionModules()
        {
            if (FlightGlobals.VesselsLoaded == null) return;

            foreach (Vessel vessel in FlightGlobals.VesselsLoaded)
                InstallInteractionModules(vessel);
        }

        private void InstallInteractionModules(Vessel vessel)
        {
            if (vessel?.parts == null) return;

            foreach (Part part in vessel.parts)
                EnsureInteractionModule(part);
        }

        private void EnsureInteractionModule(Part part)
        {
            if (!ShouldAttachInteractionModule(part)) return;
            if (part.FindModuleImplementing<KerbalNPCModule>() != null) return;

            if (TryAddInteractionModule(part))
            {
                RefreshPartActionUi(part);
                Debug.Log($"[JebAI] Attached interaction module to {part.partInfo?.name ?? part.name}.");
            }
        }

        private static bool ShouldAttachInteractionModule(Part part)
        {
            if (part == null) return false;
            if (part.vessel != null && part.vessel.isEVA) return true;
            if (part.CrewCapacity > 0) return true;
            return part.protoModuleCrew != null && part.protoModuleCrew.Count > 0;
        }

        private static bool TryAddInteractionModule(Part part)
        {
            MethodInfo[] methods = typeof(Part).GetMethods(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            for (int i = 0; i < methods.Length; i++)
            {
                MethodInfo method = methods[i];
                if (method.Name != "AddModule" || method.IsGenericMethodDefinition)
                    continue;

                object[][] argumentSets = BuildAddModuleArgumentSets(method);
                if (argumentSets == null) continue;

                for (int argIndex = 0; argIndex < argumentSets.Length; argIndex++)
                {
                    object[] args = argumentSets[argIndex];
                    try
                    {
                        method.Invoke(part, args);
                    }
                    catch (TargetInvocationException tie)
                    {
                        Debug.LogWarning(
                            $"[JebAI] AddModule invocation failed on {part.partInfo?.name ?? part.name} via {DescribeSignature(method)}: {tie.InnerException?.Message ?? tie.Message}");
                        continue;
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogWarning(
                            $"[JebAI] AddModule invocation failed on {part.partInfo?.name ?? part.name} via {DescribeSignature(method)}: {e.Message}");
                        continue;
                    }

                    if (part.FindModuleImplementing<KerbalNPCModule>() != null)
                        return true;
                }
            }

            Debug.LogWarning(
                $"[JebAI] Could not attach KerbalNPCModule to {part.partInfo?.name ?? part.name}. Available overloads: {DescribeAddModuleOverloads(methods)}");
            return false;
        }

        private static object[][] BuildAddModuleArgumentSets(MethodInfo method)
        {
            ParameterInfo[] parameters = method.GetParameters();
            if (parameters.Length == 0 || parameters.Length > 2)
                return null;

            object primaryArg;
            Type primaryType = parameters[0].ParameterType;
            if (primaryType == typeof(string))
            {
                primaryArg = nameof(KerbalNPCModule);
            }
            else if (primaryType == typeof(System.Type))
            {
                primaryArg = typeof(KerbalNPCModule);
            }
            else if (typeof(ConfigNode).IsAssignableFrom(primaryType))
            {
                var node = new ConfigNode("MODULE");
                node.AddValue("name", nameof(KerbalNPCModule));
                primaryArg = node;
            }
            else
            {
                return null;
            }

            if (parameters.Length == 1)
                return new[] { new[] { primaryArg } };

            if (parameters[1].ParameterType != typeof(bool))
                return null;

            return new[]
            {
                new object[] { primaryArg, true },
                new object[] { primaryArg, false },
            };
        }

        private static string DescribeSignature(MethodInfo method)
        {
            ParameterInfo[] parameters = method.GetParameters();
            string signature = method.Name + "(";
            for (int i = 0; i < parameters.Length; i++)
            {
                if (i > 0) signature += ", ";
                signature += parameters[i].ParameterType.Name;
            }

            return signature + ")";
        }

        private static string DescribeAddModuleOverloads(MethodInfo[] methods)
        {
            string result = string.Empty;
            bool foundAny = false;

            for (int i = 0; i < methods.Length; i++)
            {
                if (methods[i].Name != "AddModule") continue;
                if (foundAny) result += " | ";
                result += DescribeSignature(methods[i]);
                foundAny = true;
            }

            return foundAny ? result : "(none)";
        }

        private static void RefreshPartActionUi(Part part)
        {
            if (part == null) return;

            Assembly assembly = typeof(Part).Assembly;
            System.Type monoUtilities = assembly.GetType("MonoUtilities");
            if (monoUtilities == null) return;

            TryInvokeStatic(monoUtilities, "RefreshPartContextWindow", part);
            TryInvokeStatic(monoUtilities, "RefreshContextWindows", part);
            TryInvokeStatic(monoUtilities, "RefreshContextWindows");
        }

        private static void TryInvokeStatic(System.Type type, string methodName, params object[] args)
        {
            MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            for (int i = 0; i < methods.Length; i++)
            {
                MethodInfo method = methods[i];
                if (method.Name != methodName) continue;

                ParameterInfo[] parameters = method.GetParameters();
                if (parameters.Length != args.Length) continue;

                bool compatible = true;
                for (int p = 0; p < parameters.Length; p++)
                {
                    object arg = args[p];
                    if (arg == null)
                    {
                        if (parameters[p].ParameterType.IsValueType)
                        {
                            compatible = false;
                            break;
                        }

                        continue;
                    }

                    if (!parameters[p].ParameterType.IsInstanceOfType(arg))
                    {
                        compatible = false;
                        break;
                    }
                }

                if (!compatible) continue;

                try
                {
                    method.Invoke(null, args);
                    return;
                }
                catch
                {
                    return;
                }
            }
        }

    }
}
