using System;
using System.Collections.Generic;
using System.Linq;
using common;
using EagleGUIUtils;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
using TMPro;
using UnityEngine;
using FactionId = System.Int32;
using ProvinceId = System.Int32;

namespace eagle {
    public class FactionsTableController : MonoBehaviour, System.IDisposable {
        public GameObject panel;
        public GameObject factionPopup;
        public TMP_Text factionPopupText;

        private DynamicHeroTextUpdater textUpdater = new DynamicHeroTextUpdater();
        private GeneratedTextListener currentListener;

        private ProvinceView _currentProvince;
        public ProvinceView CurrentProvince {
            get => _currentProvince;
            set {
                if (!ReferenceEquals(_currentProvince, value)) {
                    _currentProvince = value;
                    UpdateTables();
                }
            }
        }

        public void LeftTableFactionHoverRowChanged(int? newIndex) {
            if (newIndex is {} index) {
                HoveredFactionChanged(DisplayedFactions.ElementAt(index * 2).Id);
            } else {
                HoveredFactionChanged(null);
            }
        }

        public void RightTableFactionHoverRowChanged(int? newIndex) {
            if (newIndex is {} index) {
                HoveredFactionChanged(DisplayedFactions.ElementAt(index * 2 + 1).Id);
            } else {
                HoveredFactionChanged(null);
            }
        }

        public void LeftTableFactionLongHoverRowChanged(int? newIndex) {
            if (newIndex is {} index) {
                LongHoveredFactionChanged(DisplayedFactions.ElementAt(index * 2).Id);
            } else {
                LongHoveredFactionChanged(null);
            }
        }

        public void RightTableFactionLongHoverRowChanged(int? newIndex) {
            if (newIndex is {} index) {
                LongHoveredFactionChanged(DisplayedFactions.ElementAt(index * 2 + 1).Id);
            } else {
                LongHoveredFactionChanged(null);
            }
        }

        private void HoveredFactionChanged(FactionId? newFid) {
            if (newFid is {} fid) {
                mapController.OverrideTargetedProvinces = Model.ProvincesForFaction(fid).ToList();
            } else {
                mapController.OverrideTargetedProvinces = new List<ProvinceId>();
            }
        }

        private string ColoredFactionString(FactionId fid) {
            var color = Color.Lerp(PlayerColors.LightPlayerColor(fid), Color.black, 0.5f);
            var factionName = GetResolvedFactionName(fid);
            return GUIUtils.ColoredString(color, factionName);
        }

        private string GetResolvedFactionName(FactionId fid) {
            var faction = Model.MaybeDestroyedFaction(fid);
            var template = DisplayNames.FactionStringTemplate(faction, Model);

            if (template.Contains("{FactionLeaderName}")) {
                var hero = Model.Heroes[faction.FactionHeadId];
                var textEntry = ClientTextProvider.Provider.GetTextEntry(hero.NameTextId);
                if (textEntry != null && !string.IsNullOrEmpty(textEntry.Text)) {
                    return template.Replace("{FactionLeaderName}", textEntry.Text);
                } else {
                    return template.Replace("{FactionLeaderName}", "Leader");
                }
            }

            return template;
        }

        private string GetResolvedAlternateFactionName(FactionView faction) {
            var template = DisplayNames.AlternateFactionStringTemplate(faction, Model);

            if (template.Contains("{FactionLeaderName}")) {
                var hero = Model.Heroes[faction.FactionHeadId];
                var textEntry = ClientTextProvider.Provider.GetTextEntry(hero.NameTextId);
                if (textEntry != null && !string.IsNullOrEmpty(textEntry.Text)) {
                    return template.Replace("{FactionLeaderName}", textEntry.Text);
                } else {
                    return template.Replace("{FactionLeaderName}", "Leader");
                }
            }

            return template;
        }

        private void LongHoveredFactionChanged(FactionId? newFid) {
            if (newFid is {} fid) {
                var faction = Model.MaybeDestroyedFaction(fid);
                var baseText = $"{ColoredFactionString(fid)} is led by ";

                string frText = "";
                foreach (var fr in faction.FactionRelationships) {
                    switch (fr.RelationshipLevel) {
                        case FactionRelationshipView.Types.RelationshipLevel.Unknown: break;
                        case FactionRelationshipView.Types.RelationshipLevel.Ally:
                            frText +=
                                    $"\nThey are allied with {ColoredFactionString(fr.TargetFactionId)}.";
                            break;
                        case FactionRelationshipView.Types.RelationshipLevel.Truce:
                            frText +=
                                    $"\nThey have a truce with {ColoredFactionString(fr.TargetFactionId)}.";
                            break;
                        case FactionRelationshipView.Types.RelationshipLevel.Hostile: break;
                        default: throw new ArgumentOutOfRangeException();
                    }
                }

                var fullText = baseText + "{FactionLeader}.";
                if (frText != "") { fullText += "\n" + frText; }

                if (currentListener != null) {
                    ClientTextProvider.Provider.RemoveListener(currentListener);
                }

                var hero = Model.Heroes[faction.FactionHeadId];
                var textEntry = ClientTextProvider.Provider.GetTextEntry(hero.NameTextId);
                if (textEntry != null && !string.IsNullOrEmpty(textEntry.Text)) {
                    factionPopupText.text = fullText.Replace("{FactionLeader}", textEntry.Text);
                } else {
                    factionPopupText.text = fullText.Replace("{FactionLeader}", "Leader");

                    currentListener = new GeneratedTextListener(
                            hero.NameTextId,
                            baseText,
                            ".",
                            0,
                            (index, text) => {
                                var updatedText = baseText + text + ".";
                                if (frText != "") { updatedText += "\n" + frText; }
                                factionPopupText.text = updatedText;
                            });
                    ClientTextProvider.Provider.AddListener(currentListener);
                }

                factionPopup.SetActive(true);
            } else {
                if (currentListener != null) {
                    ClientTextProvider.Provider.RemoveListener(currentListener);
                    currentListener = null;
                }
                factionPopup.SetActive(false);
            }
        }

        private bool DisplayFactionName => PlayerPrefs.GetInt("DisplayFactionName", 1) != 0;

        private IGameModel _model;
        public IGameModel Model { private
            get => _model;
            set {
                _model = value;
                UpdateTables();
                panel.SetActive(value != null);
            }
        }

        public EventBasedTable leftFactionsTable;
        public EventBasedTable rightFactionsTable;

        public CommandPanelController commandPanelController;
        public MapController mapController;

        private CommandSelector CommandSelector =>
                commandPanelController == null ? null : commandPanelController.Selector;

        public void UpdateTables() {
            if (Model == null) {
                panel.SetActive(false);
                return;
            }
            panel.SetActive(true);
            SetUpFactions();
        }

        private int ProvinceCount(FactionView faction) {
            return Model.Provinces.Values.Count(pv => pv.RulingFactionId == faction.Id);
        }

        private FactionRelationshipView ExistingRelationship(FactionView faction) {
            if (Model.PlayerId is FactionId myFid) {
                return Model.MaybeDestroyedFaction(myFid).FactionRelationships.FirstOrDefault(
                        t => t.TargetFactionId == faction.Id);
            } else
                return null;
        }

        private string DiploString(DiplomacyOffer offer) {
            switch (offer.OfferDetails.SealedValueCase) {
                case DiplomacyOfferDetails.SealedValueOneofCase.TruceOffer: return "Sending treaty";
                case DiplomacyOfferDetails.SealedValueOneofCase.Invitation:
                    return "Sending invitation";
                case DiplomacyOfferDetails.SealedValueOneofCase.RansomOffer:
                    return "Sending ransom offer";
            }

            return "";
        }

        private void SetUpFactionRow(FactionView faction, FactionTableRow row) {
            row.Faction = GetResolvedFactionName(faction.Id);
            row.Tooltip = GetResolvedAlternateFactionName(faction);
            row.FactionColor = PlayerColors.LightPlayerColor(faction.Id);
            row.ProvinceCount = ProvinceCount(faction);

            var diploStrings = new List<string>();

            FactionRelationshipView relationship = ExistingRelationship(faction);
            if (relationship != null) {
                switch (relationship.RelationshipLevel) {
                    case FactionRelationshipView.Types.RelationshipLevel.Truce:
                        diploStrings.Add(
                                $"Truce until {DisplayNames.DateString(relationship.ResetDate)}");
                        break;

                    case FactionRelationshipView.Types.RelationshipLevel.Ally:
                        diploStrings.Add($"Allied");
                        break;

                    default: break;
                }
            }

            var incomingOffers = faction.IncomingDiplomacyOffers;
            diploStrings.AddRange(
                    incomingOffers.Where(x => x.OriginatingFactionId == Model.PlayerId)
                            .Select(diploOffer => DiploString(diploOffer)));

            row.TruceStatus = String.Join('\n', diploStrings);

            if (CommandSelector != null) {
                row.Selectable = CommandSelector.FactionIsTargetable(faction.Id);
                row.Selected = CommandSelector.TargetedFactionIds.Contains(faction.Id);
            }
        }

        private void SetUpFactions() {
            var displayedFactions = DisplayedFactions.ToList();

            leftFactionsTable.RowCount = (displayedFactions.Count + 1) / 2;
            rightFactionsTable.RowCount = (displayedFactions.Count) / 2;

            for (int i = 0; i < (displayedFactions.Count + 1) / 2; i++) {
                var newLeftRow = leftFactionsTable.ComponentAt<FactionTableRow>(i);
                SetUpFactionRow(displayedFactions.ElementAt(2 * i), newLeftRow);

                if (2 * i + 1 < displayedFactions.Count) {
                    var newRightRow = rightFactionsTable.ComponentAt<FactionTableRow>(i);
                    SetUpFactionRow(displayedFactions.ElementAt(2 * i + 1), newRightRow);
                }
            }
        }

        public void ProvinceHovered(ProvinceId? pid) {
            // Highlight faction table
            FactionId? fid = null;
            if (pid is ProvinceId hoverPid) { fid = Model.Provinces[hoverPid].RulingFactionId; }

            for (int i = 0; i < leftFactionsTable.RowCount; i++) {
                leftFactionsTable.ComponentAt<FactionTableRow>(i).ShadeOff();
            }
            for (int i = 0; i < rightFactionsTable.RowCount; i++) {
                rightFactionsTable.ComponentAt<FactionTableRow>(i).ShadeOff();
            }
            if (fid is FactionId factionId) {
                var index = DisplayedFactions.FindIndex(f => f.Id == factionId);
                if (index != -1) {
                    if (index % 2 == 0) {
                        leftFactionsTable.ComponentAt<FactionTableRow>(index / 2).ShadeOn();
                    } else {
                        rightFactionsTable.ComponentAt<FactionTableRow>(index / 2).ShadeOn();
                    }
                }
            }
        }

        public void FactionLeftRowClicked(int index) {
            var factionId = DisplayedFactions.ElementAt(2 * index).Id;
            if (CommandSelector != null && CommandSelector.FactionIsTargetable(factionId)) {
                CommandSelector.AddTargetedFaction(factionId);
            }
            SetUpFactions();
        }

        public void FactionRightRowClicked(int index) {
            var factionId = DisplayedFactions.ElementAt(2 * index + 1).Id;
            if (CommandSelector != null && CommandSelector.FactionIsTargetable(factionId)) {
                CommandSelector.AddTargetedFaction(factionId);
            }
            SetUpFactions();
        }

        void Start() { panel.SetActive(Model != null); }

        public void FactionLabelClicked() {
            DisplayNames.DisplayFactionName = !DisplayNames.DisplayFactionName;
            GetComponentInParent<EagleGameController>().ReloadFactionDisplaySetting();
        }

        private IEnumerable<FactionView> ActiveFactions => Model.ActiveFactions.Values;
        private List<FactionView> DisplayedFactions =>
                ActiveFactions.OrderBy(fac => fac.Id == Model.PlayerId)
                        .ThenBy(fac => (ExistingRelationship(fac)?.RelationshipLevel ??
                                        FactionRelationshipView.Types.RelationshipLevel.Hostile) !=
                                       FactionRelationshipView.Types.RelationshipLevel.Hostile)
                        .ThenBy(fac => ExistingRelationship(fac)?.ResetDate?.Year ?? 0)
                        .ThenBy(fac => ExistingRelationship(fac)?.ResetDate?.Month ?? 0)
                        .ToList();

        public void Dispose() {
            if (currentListener != null) {
                ClientTextProvider.Provider.RemoveListener(currentListener);
                currentListener = null;
            }
            textUpdater?.Dispose();
        }

        private void OnDestroy() { Dispose(); }
    }
}