using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UIElements;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Objects.Item.Events;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
using VOID.Generic.Objects.Unit.Events;
using VOID.Generic.Objects.Usable.Events;
using VOID.Generic.Objects.Wearable.Events;
using VOID.Generic.Player;

namespace VOID.UserInterface.Game.GameLog
{
    public class GameLogUI : MonoBehaviour
    {
        public static GameLogUI current { get; private set; }
        
        // Configuration
        [SerializeField, MinValue(10)] private int _messageLimit = 100;
        [SerializeField] private string SENDER_PREFIX = "<b>{0}:<b> ";
        [SerializeField] private string STATUS_START_MESSAGE = "Got status <u>{0}</u> for <u>{1} turns</u>.";
        [SerializeField] private string STATUS_END_MESSAGE = "Status <u>{0}</u> ended.";
        [SerializeField] private string DEATH_MESSAGE = "Died!";
        [SerializeField] private string DAMAGED_MESSAGE = "Received <u>{0} {1}</u> damage from <u>{2}</u>.";
        [SerializeField] private string DAMAGE_MESSAGE = "Dealt <u>{0} {1}</u> damage to <u>{2}</u>.";
        [SerializeField] private string ABILITY_CAST_MESSAGE = "Casted <u>{0}</u>.";
        [SerializeField] private string TAKE_MESSAGE = "Took <u>{0}</u>.";
        [SerializeField] private string DROP_MESSAGE = "Dropped <u>{0}</u>.";
        [SerializeField] private string EQUIP_MESSAGE = "Equipped <u>{0}</u>.";
        [SerializeField] private string UNEQUIP_MESSAGE = "Unequipped <u>{0}</u>.";
        [SerializeField] private string USE_MESSAGE = "Used <u>{0}</u>.";
        [SerializeField] private string ACTION_ERROR_MESSAGE = "Action prevented: <u>{0}</u>.";
        
        // Message template
        public VisualTreeAsset messageTemplate => _messageTemplate;
        [SerializeField, Required] private VisualTreeAsset _messageTemplate;

        // Visual Elements
        private VisualElement _messageWrapper;
        private Scroller _scroller;
        private readonly List<GameLogMessageUI> _messages = new();
        
        // Units which we listen to
        private UnitObject _mainUnit;
        private UnitObject _subUnit;
        
        private void Awake()
        {
            current = this;

            var uiDocument = gameObject.GetComponent<UIDocument>();
            var templateElement = uiDocument.rootVisualElement;
            _messageWrapper = templateElement.Q("message-wrapper");
            _messageWrapper.Clear();
        }

        private void Start()
        {
            PlayerController.current.OnMainUnitChange += OnMainUnitChange;
            PlayerController.current.OnSubUnitChange += OnSubUnitChange;
        }

        private void OnMainUnitChange(UnitObject unit)
        {
            if (_mainUnit) StopListenFor(_mainUnit);
            _mainUnit = unit;
            if (unit) ListenFor(unit);
        }

        private void OnSubUnitChange(UnitObject unit)
        {
            if (_subUnit) StopListenFor(_subUnit);
            _subUnit = unit;
            if (unit) ListenFor(unit);
        }

        private void ListenFor(UnitObject unit)
        {
            unit.basicEvents.onStatusStart.AddListener(OnStatusStart);
            unit.basicEvents.onStatusEnd.AddListener(OnStatusEnd);
            unit.basicEvents.onDeathAfter.AddListener(OnDeathAfter);
            unit.basicEvents.onDamagedAfter.AddListener(OnDamagedAfter);
            unit.unitEvents.onDamageAfter.AddListener(OnDamageAfter);
            unit.unitEvents.onStartAbilityAfter.AddListener(OnAbilityCastAfter);
            unit.unitEvents.onTakeAfter.AddListener(OnTakeAfter);
            unit.unitEvents.onDropAfter.AddListener(OnDropAfter);
            unit.unitEvents.onEquipAfter.AddListener(OnEquipAfter);
            unit.unitEvents.onUnEquipAfter.AddListener(OnUnEquipAfter);
            unit.unitEvents.onUseAfter.AddListener(OnUseAfter);
            unit.unitEvents.onActionError.AddListener(OnActionError);
        }

        private void StopListenFor(UnitObject unit)
        {
            unit.basicEvents.onStatusStart.RemoveListener(OnStatusStart);
            unit.basicEvents.onStatusEnd.RemoveListener(OnStatusEnd);
            unit.basicEvents.onDeathAfter.RemoveListener(OnDeathAfter);
            unit.basicEvents.onDamagedAfter.RemoveListener(OnDamagedAfter);
            unit.unitEvents.onDamageAfter.RemoveListener(OnDamageAfter);
            unit.unitEvents.onStartAbilityAfter.RemoveListener(OnAbilityCastAfter);
            unit.unitEvents.onTakeAfter.RemoveListener(OnTakeAfter);
            unit.unitEvents.onDropAfter.RemoveListener(OnDropAfter);
            unit.unitEvents.onEquipAfter.RemoveListener(OnEquipAfter);
            unit.unitEvents.onUnEquipAfter.RemoveListener(OnUnEquipAfter);
            unit.unitEvents.onUseAfter.RemoveListener(OnUseAfter);
            unit.unitEvents.onActionError.RemoveListener(OnActionError);
        }

        private void AddError(string richText)
        {
            AddMessage($"<color=#D33>{richText}</color>");
        }

        private void AddMessage(string richText)
        {
            var message = new GameLogMessageUI();
            message.InitNew(_messageWrapper, richText);
            _messages.Add(message);
            
            if (_messages.Count > _messageLimit) RemoveLastMessage();
        }

        private void RemoveLastMessage()
        {
            var lastMessage = _messages.Last();
            lastMessage.Destroy();
            _messages.RemoveAt(_messages.Count-1);
        }
        
        /*         EVENTS BELOW        */
        /* for register and unregister */

        private void OnStatusStart(StatusEvent e)
        {
            if (e.status.hidden) return;
            AddMessage(string.Format(SENDER_PREFIX, e.obj.basicData.finalName) + string.Format(STATUS_START_MESSAGE, e.status.displayName, e.status.durationInTurns));
        }

        private void OnStatusEnd(StatusEvent e)
        {
            if (e.status.hidden) return;
            AddMessage(string.Format(SENDER_PREFIX, e.obj.basicData.finalName) + string.Format(STATUS_END_MESSAGE, e.status.displayName));
        }

        private void OnDeathAfter(DeathEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.obj.basicData.finalName) + string.Format(DEATH_MESSAGE));
        private void OnDamagedAfter(DamageEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.target.basicData.finalName) + string.Format(DAMAGED_MESSAGE, e.amount, e.damageType, e.attacker?.basicData.finalName ?? "unknown"));
        private void OnDamageAfter(DamageEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.attacker.basicData.finalName) + string.Format(DAMAGE_MESSAGE, e.amount, e.damageType, e.target.basicData.finalName));
        private void OnAbilityCastAfter(AbilityCastEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.caster.basicData.finalName) + string.Format(ABILITY_CAST_MESSAGE, e.ability.displayName));
        private void OnTakeAfter(PickEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.unit.basicData.finalName) + string.Format(TAKE_MESSAGE, e.item.basicData.finalName));
        private void OnDropAfter(DropEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.unit.basicData.finalName) + string.Format(DROP_MESSAGE, e.item.basicData.finalName));
        private void OnEquipAfter(EquipEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.unit.basicData.finalName) + string.Format(EQUIP_MESSAGE, e.wearable.basicData.finalName));
        private void OnUnEquipAfter(UnEquipEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.unit.basicData.finalName) + string.Format(UNEQUIP_MESSAGE, e.wearable.basicData.finalName));
        private void OnUseAfter(UseEvent e) => AddMessage(string.Format(SENDER_PREFIX, e.usedBy.basicData.finalName) + string.Format(USE_MESSAGE, e.usedObject.basicData.finalName));
        private void OnActionError(ActionErrorEvent e) => AddError(string.Format(SENDER_PREFIX, e.unit.basicData.finalName) + string.Format(ACTION_ERROR_MESSAGE, UnitActionErrorMessage.Get(e.cause)));
    }
}
