using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Helpers;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using System;
using System.Collections.Generic;
using System.Linq;

namespace HSUI.Interface.Party
{
    public delegate void AllianceMembersChangedEventHandler(AllianceManager sender);

    /// <summary>
    /// Provides member lists for Alliance A, B, and C when in 24-player raid content.
    /// Only populated when CrossRealm has 3 groups (alliance raid).
    /// </summary>
    public unsafe class AllianceManager : IDisposable
    {
        public static AllianceManager Instance { get; private set; } = null!;

        private readonly List<IPartyFramesMember>[] _allianceMembers = new List<IPartyFramesMember>[3];
        private int[] _lastMemberCounts = new int[3];
        private bool _wasInAllianceRaid = false;

        private AllianceManager()
        {
            for (int i = 0; i < 3; i++)
                _allianceMembers[i] = new List<IPartyFramesMember>();
        }

        public static void Initialize()
        {
            Instance = new AllianceManager();
        }

        public void Dispose()
        {
            for (int i = 0; i < 3; i++)
                _allianceMembers[i].Clear();
            Instance = null!;
        }

        /// <summary>Internal alliance index (0, 1, or 2) containing the local player, or -1 if not in alliance raid / unknown.</summary>
        public int PlayerAllianceIndex
        {
            get
            {
                var player = Plugin.ObjectTable?.LocalPlayer;
                if (player == null) return -1;
                uint playerEntityId = player.EntityId;
                ulong playerObjectId = (ulong)player.GameObjectId;

                // Primary: Check our populated member lists first - most reliable (GroupManager/CrossRealm ordering may differ)
                for (int i = 0; i < 3; i++)
                {
                    foreach (var m in _allianceMembers[i])
                    {
                        if (m.ObjectId == playerEntityId || (ulong)m.ObjectId == playerObjectId) return i;
                        if (m.Character != null && m.Character.Address == player.Address) return i;
                    }
                }

                // Fallback: InfoProxyCrossRealm.LocalPlayerGroupIndex (when in duty finder / cross-world before instance)
                var info = InfoProxyCrossRealm.Instance();
                if (info != null && info->IsInAllianceRaid && info->GroupCount >= 3)
                {
                    byte localIdx = info->LocalPlayerGroupIndex;
                    if (localIdx < 3) return localIdx;
                }

                // Fallback: CrossRealm iteration
                if (info != null && info->IsInAllianceRaid && info->GroupCount >= 3)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        var group = info->CrossRealmGroups[i];
                        for (int j = 0; j < group.GroupMemberCount; j++)
                        {
                            var member = group.GroupMembers[j];
                            if (member.EntityId == playerEntityId) return i;
                        }
                    }
                }

                // Fallback 3: Match any of our party members - we're in the same alliance as them
                var partyList = Plugin.PartyList;
                if (partyList != null)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        foreach (var m in _allianceMembers[i])
                        {
                            foreach (var pm in partyList)
                            {
                                if (pm != null && pm.EntityId == m.ObjectId)
                                {
                                    return i;
                                }
                            }
                        }
                    }
                }

                // Fallback: GroupManager - player is in MainGroup._partyMembers (their 8-man).
                // Group layout: 0,1 = other alliances from _allianceMembers; 2 = main party from _partyMembers.
                var gm = GroupManager.Instance();
                if (gm != null && gm->MainGroup.IsAlliance)
                {
                    ref var mainGroup = ref gm->MainGroup;
                    if (mainGroup.GetPartyMemberByEntityId(playerEntityId) != null)
                    {
                        return 2; // Player in main party = group 2 (our alliance)
                    }
                }

                return -1;
            }
        }

        /// <summary>Slot 0 = first other alliance, slot 1 = second. Excludes the player's alliance.</summary>
        public IReadOnlyList<IPartyFramesMember> GetMembersForOtherAlliance(int slotIndex)
        {
            if (slotIndex < 0 || slotIndex > 1) return Array.Empty<IPartyFramesMember>();
            if (!TryGetAllianceIndexForSlot(slotIndex, out int allianceIdx)) return Array.Empty<IPartyFramesMember>();
            return _allianceMembers[allianceIdx].AsReadOnly();
        }

        /// <summary>Gets the alliance index (0=A, 1=B, 2=C) for the given slot. Returns false when not in raid.</summary>
        public bool TryGetAllianceIndexForSlot(int slotIndex, out int allianceIndex)
        {
            allianceIndex = -1;
            if (slotIndex < 0 || slotIndex > 1) return false;
            int playerIdx = PlayerAllianceIndex;
            int idx = 0;
            for (int i = 0; i < 3; i++)
            {
                if (i == playerIdx) continue;
                if (idx == slotIndex)
                {
                    allianceIndex = i;
                    return true;
                }
                idx++;
            }
            return false;
        }

        /// <summary>Gets the alliance letter (A/B/C) for an internal group index. GroupManager ordering can differ from game display; use LocalPlayerGroupIndex to map when CrossRealm member data is unavailable.</summary>
        public bool TryGetAllianceLetter(int allianceIndex, out string letter)
        {
            letter = "";
            if (allianceIndex < 0 || allianceIndex > 2) return false;

            var info = InfoProxyCrossRealm.Instance();
            if (info != null && info->GroupCount >= 3)
            {
                // Check if CrossRealm has member data - when empty, we're in-instance using GroupManager (indices differ from InfoProxy)
                bool crossRealmHasData = false;
                for (int i = 0; i < 3; i++)
                {
                    if (info->CrossRealmGroups[i].GroupMemberCount > 0)
                    {
                        crossRealmHasData = true;
                        break;
                    }
                }

                if (crossRealmHasData)
                {
                    // CrossRealm populated: use EntityId lookup or direct lookup
                    uint matchEntityId = 0;
                    if (_allianceMembers[allianceIndex].Count > 0)
                        matchEntityId = _allianceMembers[allianceIndex][0].ObjectId;

                    if (matchEntityId != 0)
                    {
                        for (int crIdx = 0; crIdx < 3; crIdx++)
                        {
                            var crGroup = info->CrossRealmGroups[crIdx];
                            for (int j = 0; j < crGroup.GroupMemberCount; j++)
                            {
                                if (crGroup.GroupMembers[j].EntityId == matchEntityId)
                                {
                                    byte displayIdx = crGroup.GroupMembers[j].GroupIndex;
                                    if (displayIdx < 3)
                                    {
                                        letter = ((char)('A' + displayIdx)).ToString();
                                        return true;
                                    }
                                }
                            }
                        }
                    }

                    var group = info->CrossRealmGroups[allianceIndex];
                    if (group.GroupMemberCount > 0)
                    {
                        byte displayIdx = group.GroupMembers[0].GroupIndex;
                        if (displayIdx < 3)
                        {
                            letter = ((char)('A' + displayIdx)).ToString();
                            return true;
                        }
                    }

                    for (byte d = 0; d < 3; d++)
                    {
                        if (InfoProxyCrossRealm.GetGroupIndex(d) == allianceIndex)
                        {
                            letter = ((char)('A' + d)).ToString();
                            return true;
                        }
                    }
                }

                // CrossRealm empty (in-instance with GroupManager): use direct GM-index mapping.
                // GroupManager alliance index 0=A, 1=B, 2=C in the game's party list display order.
                letter = ((char)('A' + allianceIndex)).ToString();
                return true;
            }

            // Fallback when no CrossRealm/InfoProxy data (PvP edge cases)
            letter = ((char)('A' + allianceIndex)).ToString();
            return true;
        }

        /// <summary>True when in 24-player content with 3 alliances (PvE) or PvP Frontlines/Rival Wings.</summary>
        public bool IsInAllianceRaid
        {
            get
            {
                var gm = GroupManager.Instance();
                if (gm != null)
                {
                    // GroupManager: IsAlliance = 3x8 (alliance raids, Frontlines), IsSmallGroupAlliance = 6x4 (Rival Wings)
                    if (gm->MainGroup.IsAlliance || gm->MainGroup.IsSmallGroupAlliance)
                        return true;
                }
                var info = InfoProxyCrossRealm.Instance();
                if (info == null) return false;
                if (info->IsInAllianceRaid) return true;
                return info->IsCrossRealm && info->GroupCount >= 3;
            }
        }

        public event AllianceMembersChangedEventHandler? MembersChangedEvent;

        public void Update()
        {
            var gm = GroupManager.Instance();
            // Use GroupManager for PvE alliance raids and PvP (Frontlines, Rival Wings) - CrossRealm is not populated in PvP
            bool useGroupManager = gm != null && (gm->MainGroup.IsAlliance || gm->MainGroup.IsSmallGroupAlliance);

            if (useGroupManager)
            {
                UpdateFromGroupManager(gm);
                return;
            }

            var info = InfoProxyCrossRealm.Instance();
            if (info == null || !info->IsInAllianceRaid || info->GroupCount < 3)
            {
                if (_wasInAllianceRaid)
                {
                    _wasInAllianceRaid = false;
                    for (int i = 0; i < 3; i++)
                        _allianceMembers[i].Clear();
                    MembersChangedEvent?.Invoke(this);
                }
                return;
            }

            _wasInAllianceRaid = true;
            UpdateFromCrossRealm(info);
        }

        private void UpdateFromGroupManager(GroupManager* gm)
        {
            _wasInAllianceRaid = true;
            bool anyChanged = false;
            ref var mainGroup = ref gm->MainGroup;

            // GroupManager layout: _allianceMembers flat array (indices 0-19) has groups 0 and 1.
            // Group 2 (our party) is stored in _partyMembers. GetAllianceMemberByGroupAndIndex(0/1, slot)
            // returns empty in-instance for some game versions; use GetAllianceMemberByIndex with flat
            // indices: group 0 = indices 0-7, group 1 = indices 8-15.
            for (int allianceIdx = 0; allianceIdx < 3; allianceIdx++)
            {
                int count = 0;
                var list = new List<IPartyFramesMember>();

                if (allianceIdx == 2)
                {
                    // Our party: _partyMembers via GetPartyMemberByIndex
                    for (int slot = 0; slot < 8; slot++)
                    {
                        var pm = mainGroup.GetPartyMemberByIndex(slot);
                        if (pm == null || pm->EntityId == 0) continue;
                        var pfMember = new PartyFramesMember(
                            pm->EntityId,
                            count,
                            count,
                            EnmityLevel.Last,
                            PartyMemberStatus.None,
                            ReadyCheckStatus.None,
                            false,
                            false
                        );
                        pfMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, pm->ClassJob);
                        list.Add(pfMember);
                        count++;
                    }
                }
                else
                {
                    // Other alliances: _allianceMembers flat indices. Group 0 = 0-7, group 1 = 8-15.
                    int baseIdx = allianceIdx * 8;
                    for (int slot = 0; slot < 8; slot++)
                    {
                        var pm = mainGroup.GetAllianceMemberByIndex(baseIdx + slot);
                        if (pm == null || pm->EntityId == 0) continue;
                        var pfMember = new PartyFramesMember(
                            pm->EntityId,
                            count,
                            count,
                            EnmityLevel.Last,
                            PartyMemberStatus.None,
                            ReadyCheckStatus.None,
                            false,
                            false
                        );
                        pfMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, pm->ClassJob);
                        list.Add(pfMember);
                        count++;
                    }
                }

                if (count != _lastMemberCounts[allianceIdx])
                {
                    _allianceMembers[allianceIdx].Clear();
                    _allianceMembers[allianceIdx].AddRange(list);
                    _lastMemberCounts[allianceIdx] = count;
                    anyChanged = true;
                }
                else
                {
                    int ourIdx = 0;
                    for (int slot = 0; slot < 8 && ourIdx < _allianceMembers[allianceIdx].Count; slot++)
                    {
                        PartyMember* pm;
                        if (allianceIdx == 2)
                            pm = mainGroup.GetPartyMemberByIndex(slot);
                        else
                            pm = mainGroup.GetAllianceMemberByIndex(allianceIdx * 8 + slot);
                        if (pm == null || pm->EntityId == 0) continue;
                        if (_allianceMembers[allianceIdx][ourIdx] is PartyFramesMember pfMember)
                            pfMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, pm->ClassJob);
                        ourIdx++;
                    }
                }
            }
            if (anyChanged)
                MembersChangedEvent?.Invoke(this);
        }

        /// <summary>Dumps alliance detection and letter-mapping debug info to the log. Run /hsui debug alliance while in an alliance raid.</summary>
        public static void DumpAllianceDebugToLog()
        {
            var inst = Instance;
            if (inst == null) { Plugin.Logger.Information("[HSUI Alliance DBG] AllianceManager not initialized."); return; }

            var player = Plugin.ObjectTable?.LocalPlayer;
            uint playerEntityId = player?.EntityId ?? 0;
            string playerName = player?.Name.ToString() ?? "(null)";

            Plugin.Logger.Information("=== HSUI Alliance Debug ===");
            Plugin.Logger.Information($"[Alliance] IsInAllianceRaid={inst.IsInAllianceRaid}");
            Plugin.Logger.Information($"[Alliance] Player: EntityId={playerEntityId} Name={playerName}");

            var gm = GroupManager.Instance();
            bool useGM = gm != null && (gm->MainGroup.IsAlliance || gm->MainGroup.IsSmallGroupAlliance);
            Plugin.Logger.Information($"[Alliance] Data source: GroupManager={useGM} (IsAlliance={gm != null && gm->MainGroup.IsAlliance}, IsSmallGroupAlliance={gm != null && gm->MainGroup.IsSmallGroupAlliance})");

            var info = InfoProxyCrossRealm.Instance();
            if (info != null)
            {
                Plugin.Logger.Information($"[Alliance] InfoProxyCrossRealm: GroupCount={info->GroupCount} IsInAllianceRaid={info->IsInAllianceRaid} LocalPlayerGroupIndex={info->LocalPlayerGroupIndex}");
                if (info->GroupCount >= 3)
                {
                    for (byte d = 0; d < 3; d++)
                        Plugin.Logger.Information($"[Alliance] GetGroupIndex({d}) => internal index {InfoProxyCrossRealm.GetGroupIndex(d)}");
                }
            }
            else
                Plugin.Logger.Information("[Alliance] InfoProxyCrossRealm: null");

            int playerIdx = inst.PlayerAllianceIndex;
            Plugin.Logger.Information($"[Alliance] PlayerAllianceIndex={playerIdx} (internal index we treat as 'our' alliance, will exclude from display)");

            for (int i = 0; i < 3; i++)
            {
                int count = inst._allianceMembers[i].Count;
                string firstEntityId = count > 0 ? inst._allianceMembers[i][0].ObjectId.ToString() : "none";
                string firstName = count > 0 ? inst._allianceMembers[i][0].Name : "";
                bool hasPlayer = playerEntityId != 0 && inst._allianceMembers[i].Any(m => m.ObjectId == playerEntityId);
                string letter = inst.TryGetAllianceLetter(i, out string l) ? l : "?";
                Plugin.Logger.Information($"[Alliance] _allianceMembers[{i}]: count={count} firstEntityId={firstEntityId} firstName={firstName} hasPlayer={hasPlayer} resolvedLetter={letter}");
            }

            if (info != null && info->GroupCount >= 3)
            {
                for (int crIdx = 0; crIdx < 3; crIdx++)
                {
                    var grp = info->CrossRealmGroups[crIdx];
                    int gc = grp.GroupMemberCount;
                    string crFirstEntity = gc > 0 ? grp.GroupMembers[0].EntityId.ToString() : "none";
                    string crFirstName = gc > 0 ? grp.GroupMembers[0].NameString : "";
                    byte crGroupIndex = gc > 0 ? grp.GroupMembers[0].GroupIndex : (byte)255;
                    char crLetter = crGroupIndex < 3 ? (char)('A' + crGroupIndex) : '?';
                    Plugin.Logger.Information($"[Alliance] CrossRealmGroups[{crIdx}]: count={gc} firstEntityId={crFirstEntity} firstName={crFirstName} GroupIndex={crGroupIndex} => letter '{crLetter}'");
                }
            }

            for (int slot = 0; slot <= 1; slot++)
            {
                if (inst.TryGetAllianceIndexForSlot(slot, out int aidx) && inst.TryGetAllianceLetter(aidx, out string let))
                    Plugin.Logger.Information($"[Alliance] Display slot {slot}: allianceIndex={aidx} letter={let}");
                else
                    Plugin.Logger.Information($"[Alliance] Display slot {slot}: no data");
            }

            // Letter-resolution path debug: which code path is used and why
            bool crossRealmHasData = false;
            if (info != null && info->GroupCount >= 3)
            {
                for (int i = 0; i < 3; i++)
                {
                    if (info->CrossRealmGroups[i].GroupMemberCount > 0)
                    {
                        crossRealmHasData = true;
                        break;
                    }
                }
            }
            Plugin.Logger.Information($"[Alliance] crossRealmHasData={crossRealmHasData} → {(crossRealmHasData ? "EntityId/GetGroupIndex path" : "LocalPlayerGroupIndex path")}");

            int dbgPlayerIdx = inst.PlayerAllianceIndex;
            byte dbgLocalDisplayIdx = info != null ? info->LocalPlayerGroupIndex : (byte)255;
            Plugin.Logger.Information($"[Alliance] LocalPlayerGroupIndex mapping: playerIdx={dbgPlayerIdx} localDisplayIdx={dbgLocalDisplayIdx} (0=A,1=B,2=C for our alliance)");
            Plugin.Logger.Information($"[Alliance] Formula: displayIndex = (gmIndex - playerIdx + localDisplayIdx + 3) % 3");
            for (int i = 0; i < 3; i++)
            {
                int displayIndex = (i - dbgPlayerIdx + dbgLocalDisplayIdx + 3) % 3;
                char computedLetter = displayIndex >= 0 && displayIndex < 3 ? (char)('A' + displayIndex) : '?';
                string ours = i == dbgPlayerIdx ? " (OURS)" : "";
                Plugin.Logger.Information($"[Alliance]   GM[{i}] → displayIndex=({i}-{dbgPlayerIdx}+{dbgLocalDisplayIdx}+3)%3={displayIndex} → letter '{computedLetter}'{ours}");
            }

            Plugin.Logger.Information("=== End Alliance Debug ===");
        }

        private void UpdateFromCrossRealm(InfoProxyCrossRealm* info)
        {
            bool anyChanged = false;
            for (int allianceIdx = 0; allianceIdx < 3; allianceIdx++)
            {
                var group = info->CrossRealmGroups[allianceIdx];
                int count = group.GroupMemberCount;

                if (count != _lastMemberCounts[allianceIdx])
                {
                    _allianceMembers[allianceIdx].Clear();
                    _lastMemberCounts[allianceIdx] = count;

                    for (int i = 0; i < count; i++)
                    {
                        CrossRealmMember member = group.GroupMembers[i];
                        var partyMember = new PartyFramesMember(
                            member,
                            i,
                            i,
                            PartyMemberStatus.None,
                            ReadyCheckStatus.None,
                            member.IsPartyLeader,
                            false
                        );
                        _allianceMembers[allianceIdx].Add(partyMember);
                    }
                    anyChanged = true;
                }
                else
                {
                    for (int i = 0; i < _allianceMembers[allianceIdx].Count; i++)
                    {
                        if (_allianceMembers[allianceIdx][i] is PartyFramesMember pfMember)
                        {
                            pfMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, pfMember.IsPartyLeader, pfMember.JobId);
                        }
                    }
                }
            }
            if (anyChanged)
                MembersChangedEvent?.Invoke(this);
        }
    }
}
