using System;
using System.Collections.Generic;
using System.Linq;
using EagleGUIUtils;
using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Eagle.Api.Command.Util;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
using TMPro;
using UnityEngine.UI;

namespace eagle {
    using PlayerId = Int32;
    using ProvinceId = Int32;
    using BattalionId = Int32;
    using HeroId = Int32;
    using static SuitableBattalions.Types;

    struct Supplies {
        public int gold;
        public int food;
    }

    public class MarchCommandSelector : CommandSelector {
        private GeneratedTextListener warningTextListener;
        private string cachedWarningText = "";
        private const string NoProvinceSelectedText = "Select...";

        private bool HasNoneOption => toDropdown.options.Last().text == NoProvinceSelectedText;

        private int SelectedOriginProvinceIndex => fromDropdown.value;
        private int? SelectedDestinationProvinceIndex => HasNoneOption ? null : toDropdown.value;
        private List<CombatUnit> SelectedActingUnits => unitSelector.SelectedActingUnits;
        private Supplies SelectedSupplies => new() { gold = SelectedGold, food = SelectedFood };
        private int SelectedGold => (int)goldSlider.value;
        private int SelectedFood => (int)foodSlider.value;

        public EventBasedUnitSelector unitSelector;
        public TMP_Dropdown fromDropdown;
        public TMP_Dropdown toDropdown;
        public Slider goldSlider;
        public Slider foodSlider;
        public TMP_Text goldTakenLabel;
        public TMP_Text goldLeftLabel;
        public TMP_Text foodTakenLabel;
        public TMP_Text foodLeftLabel;

        private PlayerId _playerId;
        private int _recommendedFood;
        private int _recommendedGold;

        public override AvailableCommand.SealedValueOneofCase CommandType =>
                AvailableCommand.SealedValueOneofCase.MarchCommand;
        public override string HeaderString => "March";
        public override string CommitButtonString => "Commit March";

        private MarchAvailableCommand MarchAvailableCommand => _availableCommand.MarchCommand;
        private ProvinceId ActingProvinceId => MarchAvailableCommand.ActingProvinceId;

        public override List<HeroId> TargetedHeroIds =>
                unitSelector.SelectedIds.Where(u => u.heroId.HasValue)
                        .Select(u => u.heroId.Value)
                        .ToList();
        public override bool HeroIsTargetable(HeroId heroId) =>
                unitSelector.HeroIsTargetable(heroId);
        public override void AddTargetedHero(HeroId heroId) => unitSelector.AddTargetedHero(heroId);
        public override void RemoveTargetedHero(HeroId heroId) =>
                unitSelector.RemoveTargetedHero(heroId);

        public override List<BattalionId> TargetedBattalionIds =>
                unitSelector.SelectedIds.Where(u => u.battalion != null)
                        .Select(u => u.battalion.Id)
                        .ToList();
        public override bool BattalionIsTargetable(BattalionId bid) =>
                unitSelector.BattalionIsTargetable(bid);
        public override void AddTargetedBattalion(BattalionId bid) =>
                unitSelector.AddTargetedBattalion(bid);
        public override void RemoveTargetedBattalion(BattalionId bid) =>
                unitSelector.RemoveTargetedBattalion(bid);

        private List<MarchCommandFromOneProvince> _sortedOneProvinceCommands;
        private List<ProvinceId> _sortedDestinationProvinceIds;

        private ProvinceView OriginProvince =>
                _model.Provinces[SelectedOneProvinceCommand.OriginProvinceId];
        private ProvinceId? DestinationProvinceId {
            get {
                if (SelectedDestinationProvinceIndex is {} idx)
                    return _sortedDestinationProvinceIds[idx];
                return null;
            }
        }
        private ProvinceView DestinationProvince {
            get {
                if (DestinationProvinceId is {} dpid) { return _model.Provinces[dpid]; }
                return null;
            }
        }

        private bool WouldAbandonProvince => unitSelector.SelectedActingUnits.Count() ==
                                             OriginProvince.FullInfo.RulingFactionHeroIds.Count();

        private bool NotEnoughFood =>
                SelectedFood < Math.Min(2 * FoodCost, SelectedOneProvinceCommand.FoodAvailable);

        private PlayerId? DestinationRulingFactionId => DestinationProvince?.RulingFactionId;

        private bool MarchingOnTrucePartner => DestinationRulingFactionId is {}
        destinationFactionId && destinationFactionId != _playerId &&
                _model.MaybeDestroyedFaction(_playerId).FactionRelationships.ToList().Exists(
                        t => t.TargetFactionId == destinationFactionId &&
                             t.RelationshipLevel !=
                                     FactionRelationshipView.Types.RelationshipLevel.Hostile);

        private bool BadlyConstructedUnits =>
                unitSelector.HeroIdsLeadingInappropriateUnits.Count > 0;

        private bool NoBattalionsIntoEnemy =>
                unitSelector.BattalionCount == 0 &&
                DestinationProvince is { RulingFactionId : not null } &&
                DestinationProvince.RulingFactionId.Value != _playerId;

        public override bool WarnOnCommitButton => WouldAbandonProvince || NotEnoughFood ||
                                                   MarchingOnTrucePartner ||
                                                   BadlyConstructedUnits || NoBattalionsIntoEnemy;
        public override string CommitWarningText {
            get {
                if (!(WarnOnCommitButton)) return null;

                string txt = "";
                if (WouldAbandonProvince) { txt += "This action would abandon this province\n\n"; }
                if (NotEnoughFood) {
                    txt += $"You have not selected enough food for your battalions for 2 months ({2 * FoodCost} needed)\n\n";
                }
                if (MarchingOnTrucePartner) {
                    txt += $"You have a treaty with {_model.FactionName(DestinationRulingFactionId.Value)}.\n\n";
                } else if (NoBattalionsIntoEnemy) {
                    txt += $"{DestinationProvince.Name} is occupied by a hostile faction, and you have not selected any troops\n\n";
                }
                if (BadlyConstructedUnits) {
                    var count = unitSelector.HeroIdsLeadingInappropriateUnits.Count;
                    if (count == 1) {
                        txt += !string.IsNullOrEmpty(cachedWarningText)
                                       ? cachedWarningText
                                       : "A hero is leading a battalion type that would restrict their abilities\n\n";
                    } else {
                        txt += "Some of your heroes are leading battalion types that would restrict their abilities\n\n";
                    }
                }

                return txt;
            }
        }

        private MarchCommandFromOneProvince SelectedOneProvinceCommand =>
                _sortedOneProvinceCommands[SelectedOriginProvinceIndex];

        protected override ProvinceId? TargetedProvince {
            get {
                if (SelectedDestinationProvinceIndex is {} idx) {
                    return _sortedDestinationProvinceIds[idx];
                }
                return null;
            }

            set {
                if (value.HasValue) {
                    int index = _sortedDestinationProvinceIds.IndexOf(value.Value);
                    if (index != -1) {
                        if (HasNoneOption) {
                            toDropdown.options.RemoveAt(toDropdown.options.Count - 1);
                        }
                        toDropdown.value = index;
                    } else {
                        SetUpDestinationProvinceDropdown();
                    }
                }
            }
        }

        private int FoodCost => (int)Math.Ceiling(
                SelectedActingUnits.Where(u => u.BattalionId.HasValue)
                        .Select(u => u.BattalionId.Value)
                        .Select(bid => SelectedOneProvinceCommand.AvailableBattalions.First(
                                        bwfc => bwfc.BattalionId == bid))
                        .Select(bwfc => bwfc.MonthlyFood)
                        .Sum());

        public override bool EnableCommitButton =>
                (unitSelector.UnitsAreValid && DestinationProvinceId.HasValue &&
                 (SelectedFood >= FoodCost ||
                  SelectedFood >= SelectedOneProvinceCommand.FoodAvailable) &&
                 SelectedFood <= SelectedOneProvinceCommand.FoodAvailable &&
                 SelectedGold >= SelectedOneProvinceCommand.GoldCost &&
                 SelectedGold <= SelectedOneProvinceCommand.GoldAvailable);

        public override string DisabledCommitButtonReason {
            get {
                if (DestinationProvinceId == null) { return "Must select a destination province"; }
                if (SelectedGold > SelectedOneProvinceCommand.GoldAvailable) {
                    return "Trying to send more gold than available";
                }
                if (SelectedFood > SelectedOneProvinceCommand.FoodAvailable) {
                    return "Trying to send more food than available";
                }
                if (SelectedFood < FoodCost) { return $"Must send at least {FoodCost} food"; }

                return unitSelector.InvalidUnitsReason;
            }
        }

        private void SetUpDestinationProvinceDropdown() {
            _sortedDestinationProvinceIds = SelectedOneProvinceCommand.AvailableDestinationProvinces
                                                    .OrderBy(pid => _model.Provinces[pid].Name)
                                                    .ToList();
            var destinationProvinceNames = _sortedDestinationProvinceIds
                                                   .Select(id => GUIUtils.DarkColoredProvinceName(
                                                                   _model.Provinces[id],
                                                                   _model.PlayerId.Value))
                                                   .ToList();
            destinationProvinceNames.Add(NoProvinceSelectedText);

            toDropdown.ClearOptions();
            toDropdown.AddOptions(destinationProvinceNames);
            toDropdown.SetValueWithoutNotify(destinationProvinceNames.Count - 1);
        }

        private void SetUpUnitSelector() {
            var dict = new Dictionary<HeroId, List<BattalionIdWithSuitability>>();
            foreach (var kv in SelectedOneProvinceCommand.SuitableBattalionsForHeroes) {
                dict.Add(kv.Key, kv.Value.BattalionIdsWithSuitability.ToList());
            }

            var recommendedConfig = SelectedOneProvinceCommand.RecommendedBattleConfig;
            List<CombatUnit> recommendedUnits = recommendedConfig == null
                                                        ? new List<CombatUnit>()
                                                        : recommendedConfig.Units.ToList();
            _recommendedFood = recommendedConfig == null ? 0 : recommendedConfig.Food;
            _recommendedGold = recommendedConfig == null ? 0 : recommendedConfig.Gold;

            var province = _model.Provinces[SelectedOneProvinceCommand.OriginProvinceId];
            var availableBattalionIds =
                    SelectedOneProvinceCommand.AvailableBattalions.Select(b => b.BattalionId)
                            .ToList();
            var orderedBattalions = ProvinceUtils.OrderedBattalions(province)
                                            .Where(batt => availableBattalionIds.Contains(batt.Id))
                                            .ToList();

            unitSelector.SetUpUnits(
                    _model,
                    SelectedOneProvinceCommand.AvailableHeroIds.ToList(),
                    orderedBattalions,
                    dict,
                    recommendedUnits);
        }

        public void RecommendedButtonClicked() {
            foodSlider.value = _recommendedFood;
            goldSlider.value = _recommendedGold;
        }

        public void FromDropdownValueChanged(Int32 newValue) {
            SetUpDestinationProvinceDropdown();
            SetUpUnitSelector();

            foodSlider.minValue = FoodCost;
            foodSlider.maxValue = SelectedOneProvinceCommand.FoodAvailable;
            goldSlider.minValue = 0;
            goldSlider.maxValue = SelectedOneProvinceCommand.GoldAvailable;

            SetGoldLabels();
            SetFoodLabels();

            foodSlider.value = FoodCost;
            goldSlider.value = SelectedOneProvinceCommand.GoldCost;
        }

        public void ToDropdownValueChanged(Int32 newValue) {
            if (HasNoneOption && newValue < _sortedDestinationProvinceIds.Count) {
                toDropdown.options.RemoveAt(toDropdown.options.Count - 1);
                toDropdown.value = newValue;
            }
        }

        protected override void SetUpUI() {
            _sortedOneProvinceCommands =
                    MarchAvailableCommand.OneProvinceCommands
                            .OrderBy(cmd => _model.Provinces[cmd.OriginProvinceId].Name)
                            .ToList();

            var availableOriginProvinceNames =
                    _sortedOneProvinceCommands
                            .Select(cmd => _model.Provinces[cmd.OriginProvinceId].Name)
                            .ToList();

            fromDropdown.ClearOptions();
            fromDropdown.AddOptions(availableOriginProvinceNames);
            fromDropdown.onValueChanged.RemoveAllListeners();
            fromDropdown.onValueChanged.AddListener(FromDropdownValueChanged);

            var thisProvinceIndex = _sortedOneProvinceCommands.FindIndex(
                    cmd => cmd.OriginProvinceId == ActingProvinceId);
            if (thisProvinceIndex == -1) {
                fromDropdown.value = 0;
            } else {
                fromDropdown.value = thisProvinceIndex;
            }

            SetUpDestinationProvinceDropdown();
            SetUpUnitSelector();

            _playerId = _model.PlayerId.Value;

            // TODO: replace with min supplies from proto
            foodSlider.minValue = 0;
            goldSlider.minValue = 0;
            foodSlider.maxValue = SelectedOneProvinceCommand.FoodAvailable;
            goldSlider.maxValue = SelectedOneProvinceCommand.GoldAvailable;

            foodSlider.value = 0;
            goldSlider.value = Math.Min(
                    SelectedOneProvinceCommand.GoldCost,
                    SelectedOneProvinceCommand.GoldAvailable);

            SetFoodLabels();
            SetGoldLabels();

            SetupWarningTextListener();
        }

        private void ClearWarningTextListener() {
            if (warningTextListener != null) {
                ClientTextProvider.Provider.RemoveListener(warningTextListener);
                warningTextListener = null;
            }
        }

        private void SetupWarningTextListener() {
            ClearWarningTextListener();

            // Only set up if there's exactly one hero with inappropriate units
            if (unitSelector.HeroIdsLeadingInappropriateUnits.Count == 1) {
                var heroId = unitSelector.HeroIdsLeadingInappropriateUnits[0];
                var hero = _model.Heroes[heroId];

                if (!string.IsNullOrEmpty(hero.NameTextId)) {
                    warningTextListener = new GeneratedTextListener(
                            hero.NameTextId,
                            "",
                            " is leading a battalion type that would restrict their abilities\n\n",
                            0,
                            (index, text) => { cachedWarningText = text; });

                    ClientTextProvider.Provider.AddListener(warningTextListener);
                }
            }
        }

        public void GoldMinusClicked() {
            goldSlider.value = Math.Max(0, goldSlider.value - KeyModifiedAmount.Amount());
            SetGoldLabels();
        }

        public void GoldPlusClicked() {
            goldSlider.value =
                    Math.Min(goldSlider.maxValue, goldSlider.value + KeyModifiedAmount.Amount());
            SetGoldLabels();
        }

        public void OnGoldSliderChanged(float value) { SetGoldLabels(); }

        public void FoodMinusClicked() {
            foodSlider.value = Math.Max(FoodCost, foodSlider.value - KeyModifiedAmount.Amount());
            SetFoodLabels();
        }

        public void FoodPlusClicked() {
            foodSlider.value =
                    Math.Min(foodSlider.maxValue, foodSlider.value + KeyModifiedAmount.Amount());
            SetFoodLabels();
        }

        public void OnFoodSliderChanged(float value) { SetFoodLabels(); }

        private void SetGoldLabels() {
            int taken = (int)goldSlider.value;
            int left = (int)goldSlider.maxValue - taken;
            goldTakenLabel.text = taken.ToString();
            goldLeftLabel.text = left.ToString();
        }

        private void SetFoodLabels() {
            int taken = (int)foodSlider.value;
            int left = (int)foodSlider.maxValue - taken;
            foodTakenLabel.text = taken.ToString();
            foodLeftLabel.text = left.ToString();
        }

        public override SelectedCommand Command {
            get {
                if (DestinationProvinceId is {} dpid) {
                    var cmd = new SelectedCommand {
                        MarchCommand =
                                new MarchSelectedCommand {
                                    DestinationProvinceId = dpid,
                                    Food = SelectedSupplies.food,
                                    Gold = SelectedSupplies.gold,
                                    OriginProvince = SelectedOneProvinceCommand.OriginProvinceId
                                }
                    };
                    cmd.MarchCommand.MarchingUnits.AddRange(SelectedActingUnits);

                    return cmd;
                }
                return null;
            }
        }

        void OnDestroy() { ClearWarningTextListener(); }
    }
}
