using System;
using System.Collections.Generic;
using System.Linq;
using Eagle0.Tutorial;
using Net.Eagle0.Common;
using Net.Eagle0.Eagle.Views;
using Shardok;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace eagle {
    public class BattleProgressRowController : MonoBehaviour {
        private const string FightButtonTutorialId = "FightButton";

        public TMP_Text provinceLabel;
        public TMP_Text defenderProvinceLabel;
        public BattleThermometer thermometer;
        public Button actionButton;
        public TMP_Text actionButtonLabel;

        private bool _registeredAsFightButton;

        public void
        Populate(ShardokBattleView battle, IGameModel model, Action<string> goToBattleAction) {
            model.Provinces.TryGetValue(battle.DefenderProvince, out var pv);
            provinceLabel.text =
                    RetreatingLabel(AttackerOriginsText(battle, model), battle.AttackerRetreating);
            defenderProvinceLabel.text = RetreatingLabel(
                    pv != null ? pv.Name : $"Province {battle.DefenderProvince}",
                    battle.DefenderRetreating);

            bool isSelf = battle.PlayerInfos.Any(pi => pi.Hostility == Hostility.SelfHostility);
            bool isAllied = isSelf ||
                            battle.PlayerInfos.Any(pi => pi.Hostility == Hostility.AlliedHostility);

            Color defColor;
            IList<Color> atkColors;
            if (pv != null && pv.RulingFactionId.HasValue) {
                int defFactionId = pv.RulingFactionId.Value;
                defColor = PlayerColors.LightPlayerColor(defFactionId);
                atkColors = battle.PlayerInfos.Where(pi => pi.EagleFactionId != defFactionId)
                                    .Select(pi => pi.EagleFactionId)
                                    .Distinct()
                                    .OrderBy(id => id)
                                    .Select(id => PlayerColors.LightPlayerColor(id))
                                    .ToList();
                if (atkColors.Count == 0)
                    atkColors = new List<Color> { PlayerColors.LightPlayerColor(1) };
            } else {
                defColor = PlayerColors.LightPlayerColor(0);
                atkColors = new List<Color> { PlayerColors.LightPlayerColor(1) };
            }

            if (isAllied && battle.DefenderTroopCount.HasValue &&
                battle.AttackerTroopCount.HasValue) {
                thermometer.UpdateBalance(
                        battle.DefenderTroopCount.Value,
                        battle.AttackerTroopCount.Value,
                        defColor,
                        atkColors);
                thermometer.attackerLabel.text = battle.AttackerTroopCount.Value.ToString();
                thermometer.defenderLabel.text = battle.DefenderTroopCount.Value.ToString();
            } else {
                thermometer.UpdateBalance(battle.DefenderRatio, defColor, atkColors);
                thermometer.attackerLabel.text = "???";
                thermometer.defenderLabel.text = "???";
            }

            actionButton.onClick.RemoveAllListeners();
            var gameId = battle.ShardokGameId;
            bool modelReady = model.ShardokGameModels.ContainsKey(gameId);

            if (isSelf) {
                bool canFight = battle.CanFight && modelReady;
                SetActionButtonVisible(true);
                actionButton.interactable = canFight;
                actionButtonLabel.text =
                        modelReady ? battle.CanFight ? "Fight!" : "Waiting..." : "Loading...";
                if (canFight) {
                    actionButton.onClick.AddListener(() => goToBattleAction?.Invoke(gameId));
                }
                SetFightButtonTutorialRegistration(canFight);
            } else if (isAllied) {
                // ShardokBattles is populated when a NewBattle eagle update arrives, but the
                // matching ShardokGameModel isn't created until the first action-result
                // response for that battle reaches us. During that gap, GoToBattle would
                // throw on Model.ShardokGameModels[gameId]; gate the button on the model
                // existing so the click can never land in the gap.
                bool canObserve = battle.CanObserve && modelReady;
                SetActionButtonVisible(true);
                actionButton.interactable = canObserve;
                actionButtonLabel.text =
                        modelReady ? battle.CanObserve ? "Observe" : "Waiting..." : "Loading...";
                if (canObserve) {
                    actionButton.onClick.AddListener(() => goToBattleAction?.Invoke(gameId));
                }
                SetFightButtonTutorialRegistration(false);
            } else {
                SetActionButtonVisible(false);
                SetFightButtonTutorialRegistration(false);
            }
        }

        private void SetActionButtonVisible(bool visible) {
            actionButton.gameObject.SetActive(true);
            actionButton.interactable = visible;
            actionButton.targetGraphic.enabled = visible;
            actionButtonLabel.enabled = visible;
            if (!visible) {
                actionButton.onClick.RemoveAllListeners();
                actionButtonLabel.text = "";
            }
        }

        private static string AttackerOriginsText(ShardokBattleView battle, IGameModel model) {
            var attackerOrigins = AttackerOriginLabels(battle, model);
            if (!attackerOrigins.Any()) { return ""; }
            return string.Join(", ", attackerOrigins);
        }

        private static IEnumerable<string> AttackerOriginLabels(
                ShardokBattleView battle,
                IGameModel model) {
            return battle.PlayerInfos.Where(pi => !pi.IsDefender)
                    .SelectMany(pi => pi.OriginProvinceIds)
                    .Where(pid => pid != battle.DefenderProvince)
                    .Distinct()
                    .OrderBy(
                            pid => model.Provinces.TryGetValue(pid, out var p) ? p.Name
                                                                               : $"Province {pid}")
                    .Select(pid => ProvinceName(model, pid));
        }

        private static string ProvinceName(IGameModel model, int provinceId) {
            if (model.Provinces.TryGetValue(provinceId, out var province)) { return province.Name; }

            return $"Province {provinceId}";
        }

        private static string RetreatingLabel(string label, bool isRetreating) {
            if (!isRetreating) return label;
            return string.IsNullOrEmpty(label) ? "Retreating" : $"{label} (Retreating)";
        }

        private void SetFightButtonTutorialRegistration(bool shouldRegister) {
            var registry = TutorialManager.Instance?.TargetRegistry;
            if (registry == null) return;

            if (shouldRegister) {
                registry.RegisterTarget(
                        FightButtonTutorialId,
                        actionButton.GetComponent<RectTransform>());
                _registeredAsFightButton = true;
            } else if (_registeredAsFightButton) {
                registry.UnregisterTarget(FightButtonTutorialId);
                _registeredAsFightButton = false;
            }
        }

        private void OnDestroy() {
            if (_registeredAsFightButton) {
                TutorialManager.Instance?.TargetRegistry?.UnregisterTarget(FightButtonTutorialId);
                _registeredAsFightButton = false;
            }
        }
    }
}
