using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Gyvr.Mythril2D
{
    [Serializable]
    public class MountSummonAbilityDataBlock : ActiveAbilityBaseDataBlock
    {
        public CharacterBaseDataBlock mountData;
    }

    public class MountSummonAbility : ActiveAbility<MountSummonAbilitySheet>
    {
        private HashSet<CharacterBase> m_summons = new();

        [Header("Reference")]
        [SerializeField] private Animator m_animator = null;

        [Header("Parameters")]
        [SerializeField] private string m_fireAnimationParameter = "fire";
        [SerializeField] private bool canRideIndoors = false;
        [SerializeField] private string m_noMountIndoors = "blockmount";
        [SerializeField] private float m_summonDistance = 10f;

        protected override Type GetDataBlockType() => typeof(MountSummonAbilityDataBlock);

        public override void Init(CharacterBase character, AbilitySheet settings)
        {
            base.Init(character, settings);

            Debug.Assert(m_animator, ErrorMessages.InspectorMissingComponentReference<Animator>());
            Debug.Assert(m_animator.GetBehaviour<StateMessageDispatcher>(), $"{nameof(StateMessageDispatcher)} not found on ability animator.");
        }

        public override EAbilityFireCheckResult CanFire()
        {
            // 0) If mounted, always allow (dismount is never blocked by cooldown/mana)
            if (m_character.controller is RiderController rider0 && rider0.isRiding)
                return EAbilityFireCheckResult.Valid;

            // 1) Honor base cooldown/mana/incapacitated checks for summoning/calling
            var baseResult = base.CanFire();
            if (baseResult != EAbilityFireCheckResult.Valid)
                return baseResult;

            // 2) Can't summon indoors
            if (!canRideIndoors && GameManager.GameFlagSystem.Get(m_noMountIndoors))
            {
                #if MYTHRIL_MOUNT_EXTENDED_ENUMS
                        return EAbilityFireCheckResult.NotIndoors;
                #else
                        return EAbilityFireCheckResult.Unknown;
                #endif
            }

            return EAbilityFireCheckResult.Valid;
        }

        protected override void Fire()
        {
            // If mounted, this ability DISMOUNTS
            if (m_character.controller is RiderController rider && rider.isRiding)
            {
                var mount = UnityEngine.Object
                    .FindObjectsByType<Mount>(FindObjectsSortMode.None)
                    .FirstOrDefault(m => m.IsMounted && m.rider == m_character);

                if (mount != null)
                {
                    mount.DismountPlayer();
                }

                TerminateCasting();
                return;
            }

            // Otherwise, we are summoning / calling the mount
            if (!canRideIndoors && GameManager.GameFlagSystem.Get(m_noMountIndoors))
            {
                Debug.LogWarning("Can't summon a mount while indoors.");
                TerminateCasting();
                return;
            }

            m_animator?.SetTrigger(m_fireAnimationParameter);
        }

        public void OnCastAnimationEnded()
        {
            if (!m_character.dead)
            {
                Summon();
                TerminateCasting();
            }
        }

        public void Summon(CharacterBaseDataBlock data = null)
        {
            Vector2 direction = m_character.GetTargetDirection();

            // Try to find existing summon
            var existingMount = m_summons.FirstOrDefault(c => c != null && !c.dead);

            if (existingMount != null)
            {
                Debug.Log("[MountSummonAbility] Mount already exists, commanding it to move.");

                float dist = Vector2.Distance(existingMount.transform.position, m_character.transform.position);
                if (dist > (m_summonDistance * 1.15))
                {
                    existingMount.transform.position = GetValidSummonPosition(m_character.transform.position, m_summonDistance, GameManager.Config.collisionContactFilter.layerMask);
                }

                if (existingMount is Mount existingMountTyped)
                {
                    Vector2 toPlayer = (m_character.transform.position - existingMountTyped.transform.position);
                    Vector2 stopShort = (Vector2)m_character.transform.position - toPlayer.normalized * 0.5f;
                    m_character.StartCoroutine(RunInAndIdle(existingMountTyped, stopShort));
                }

                return;
            }

            GameObject prefabToSummon = activeAbilitySheet.mountPrefab;

            if (prefabToSummon == null)
            {
                Debug.LogWarning("MountSummonAbility: No mount prefab provided or previously mounted.");
                return;
            }

            Vector2 summonPos = GetValidSummonPosition(m_character.transform.position, m_summonDistance, GameManager.Config.collisionContactFilter.layerMask);

            Persistable persistable = GameManager.PersistenceSystem.InstantiateCustom(
                prefabToSummon,
                summonPos,
                Quaternion.identity,
                m_character.transform,
                data != null && data.info is IIdentifiablePersistentDataHandler identifiable
                    ? identifiable.GetIdentifier()
                    : null
            );

            persistable.transform.parent = null;
            persistable.transform.position = summonPos;

            CharacterBase character = persistable as CharacterBase;
            Debug.Assert(character != null, "MountSummonAbility: Summoned prefab must have a CharacterBase component.");

            if (data != null)
            {
                character.LoadDataBlock(data);
            }

            m_summons.Add(character);
            character.FlagAsSummoned();

            if (character is Mount mount && !mount.IsMounted)
            {
                Vector2 toPlayer = (m_character.transform.position - mount.transform.position);
                Vector2 stopShort = (Vector2)m_character.transform.position - toPlayer.normalized * 0.5f;
                m_character.StartCoroutine(RunInAndIdle(mount, stopShort));
            }

            character.destroyedEvent.AddListener(() => m_summons.Remove(character));
        }

        private IEnumerator RunInAndIdle(Mount mount, Vector2 destination)
        {
            var tcs = mount.MoveTo(destination);
            yield return new WaitUntil(() => tcs.Task.IsCompleted);

            mount.MoveTo(mount.transform.position);

            mount.ResetTargetDirection();
            mount.InterruptPush();
        }

        private Vector2 GetValidSummonPosition(Vector2 origin, float maxDistance, LayerMask collisionMask)
        {
            Vector2[] directions = new Vector2[]
            {
                Vector2.up,
                new Vector2(1, 1).normalized,
                Vector2.right,
                new Vector2(1, -1).normalized,
                Vector2.down,
                new Vector2(-1, -1).normalized,
                Vector2.left,
                new Vector2(-1, 1).normalized
            };

            List<Vector2> validPositions = new List<Vector2>();
            Vector2 bestFallback = origin;
            float longestDistance = 0f;

            foreach (var dir in directions)
            {
                RaycastHit2D hit = Physics2D.Raycast(origin, dir, maxDistance, collisionMask);
                if (!hit)
                {
                    validPositions.Add(origin + dir * maxDistance);
                }
                else if (hit.distance > longestDistance)
                {
                    longestDistance = hit.distance;
                    bestFallback = origin + dir * (hit.distance - 0.75f);
                }
            }

            if (validPositions.Count > 0)
            {
                return validPositions[UnityEngine.Random.Range(0, validPositions.Count)];
            }
            else
            {
                return bestFallback;
            }
        }

        protected override void OnSave(PersistableDataBlock block)
        {
            base.OnSave(block);
            var saved = m_summons.LastOrDefault();
            block.As<MountSummonAbilityDataBlock>().mountData = saved?.CreateDataBlock().As<CharacterBaseDataBlock>();
        }

        protected override void OnLoad(PersistableDataBlock block)
        {
            base.OnLoad(block);
            var data = block.As<MountSummonAbilityDataBlock>().mountData;
            Summon(data);
        }
    }
}
