using AYellowpaper.SerializedCollections;
using UniRx;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.InputSystem;

namespace Hazards
{
    /// <summary>
    ///
    /// With the cachedOldPath and the cachedOldAction we put the hazards always on the penultimate point of the last path.
    /// 
    /// </summary>
    public class SpawnHazards : MonoBehaviour
    {
        public static SpawnHazards Instance { get; private set; }

        private ReactiveProperty<NavMeshPath> cachedOldPath;
        private ReactiveProperty<ActionAdvertiser> cachedOldAction;
        
        [SerializeField] private SerializedDictionary<string, float> spawnProbabilities;
        [SerializeField] private SerializedDictionary<string, GameObject> actionHazardMapping;

        [SerializeField] private NavMeshAgent grandmaAgent; // this is needed to compute the path behind grandma

        public ReactiveProperty<ActionAdvertiser> hazardSpawned = new ReactiveProperty<ActionAdvertiser>(null);
        
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
                return;
            }
            
            Instance = this;

            cachedOldPath = new ReactiveProperty<NavMeshPath>();
            cachedOldAction = new ReactiveProperty<ActionAdvertiser>();
            
            SubscriptionDirectSpawn();
            SubscriptionDelayedSpawn();

        }

        #region Subscription Logic
        // TODO: maybe we can combine the two subscriptions into one
        private void SubscriptionDirectSpawn()
        {
            cachedOldAction
                .Where(action => action != null) // ← Filter out null values
                .Where(action => action.actionName is "Toilet" or "Bathtub")
                .Where(action => ShouldSpawnHazard(action.actionName))
                .Delay(System.TimeSpan.FromSeconds(0.5f))
                .Subscribe(action =>
                {
                    if (actionHazardMapping.TryGetValue(action.actionName, out GameObject hazardPrefab))
                    {
                        Debug.Log($"Spawning hazard for action: {action.actionName}");
                        GameObject hazard = Instantiate(hazardPrefab);

                        hazardSpawned.Value = action;
                        
                        Observable.Timer(System.TimeSpan.FromSeconds(1f))
                            .Subscribe(_ => hazardSpawned.Value = null)
                            .AddTo(this);
                    
                        
                        // if its a fire, we spawn the fire at the location of the advertiser otherwise on the location of the cached path:
                        var spawnLocation = GetSpawnLocationOnCachedPath();
                        
                        hazard.transform.position = spawnLocation;
                    }
                    else
                    {
                        Debug.LogWarning($"No hazard mapping found for action: {action.actionName}");
                    }
                })
                .AddTo(this);
        }
        
        private bool ShouldSpawnHazard(string actionName)
        {
            if (spawnProbabilities.TryGetValue(actionName, out float probability))
            {
                float random = Random.Range(0,1);
                return random < probability;
            }
    
            // if the action is not specified than its just spawned with a probability of 1
            return true;
        }
        
        private void SubscriptionDelayedSpawn()
        {
            cachedOldAction
                .Where(action => action != null)
                .Where(action => action.actionName is "Fridge" or "Couch" or "Cooking")
                .Where(action => ShouldSpawnHazard(action.actionName))
                .SelectMany(action =>       // delay each item individually 
                    Observable.ReturnUnit()
                        .Delay(System.TimeSpan.FromSeconds(Random.Range(0.4f, 3f)))
                        .Select(_ => action)
                )
                .Subscribe(action =>
                {
                    var direction = grandmaAgent.GetComponent<GrandmaMovement>().GetMovementDirection().normalized * 0.75f;
                    var behindAgent = grandmaAgent.transform.position - direction;
                    
                    var spawnLocation = action.actionName == "Cooking"
                        ? action.transform.position + new Vector3 (0,1f,0) // small offset for the fire
                        : behindAgent;
                    
                    if (actionHazardMapping.TryGetValue(action.actionName, out GameObject hazardPrefab))
                    {
                        Debug.Log($"Spawning hazard for action: {action.actionName}");

                        GameObject hazard = null;
                        
                        // After cooking we want the fire to appear, which is spawned via the Ignitable class
                        if (action.actionName != "Cooking")
                        {
                            hazard = Instantiate(hazardPrefab);   
                        }

                        if (action.actionName == "Couch" || action.actionName == "Fridge")
                        {
                            AudioManager.Instance.PlayThrowBanana();
                        }
     
                        // The hazard after cooking is fire for which we additionally need to check the ignitable area
                        if (action.actionName == "Cooking")
                        {
                            // Check all colliders in a small radius
                            var hits = Physics2D.OverlapCircleAll(spawnLocation, 0.1f);
                            foreach (var hit in hits)
                            {
                                var ignitable = hit.GetComponent<Ignitable>();
                                if (ignitable != null)
                                {
                                    ignitable.Ignite();
                                    break; 
                                }
                            }
                        } 
                        
                        hazardSpawned.Value = action;
                        
                        Observable.Timer(System.TimeSpan.FromSeconds(1f))
                            .Subscribe(_ => hazardSpawned.Value = null)
                            .AddTo(this);
                        
                        if(hazard != null)
                            hazard.transform.position = spawnLocation;
                    }
                    else
                    {
                        Debug.LogWarning($"No hazard mapping found for action: {action.actionName}");
                    }   
                });
        }
        #endregion
        

        public void CacheOldPath(NavMeshPath oldPath)
        {
            cachedOldPath.Value = oldPath;
        }

        public void CacheOldAction(ActionAdvertiser action)
        {
            cachedOldAction.Value = action;
        }
        
        /// <summary>
        /// Returns the corner from the cachedPath. Indexing starts at the last index,
        /// so that 0 is the last corner, 1 is the penultimate corner and so on.
        /// </summary>
        /// <param name="fromLastIndex">This is the Length of the CachedPath - fromLastIndex, i.e. start counting from the last index</param>
        /// <returns></returns>
        public Vector3 GetSpawnLocationOnCachedPath(int fromLastIndex=1)
        {
            int pathLength = cachedOldPath.Value.corners.Length;
            return cachedOldPath.Value.corners[pathLength - fromLastIndex];
        }
        
    }
}