using Jovian.EncounterSystem;
using Jovian.ZoneSystem;
using System;
using UnityEngine;

namespace Nox.Game {
    public class EncounterHandler {
        private readonly ZoneSystem zoneSystem;
        private readonly EncounterRegistry encounterRegistry;
        private readonly EncounterView encounterView;
        private readonly AdventureData adventureData;
        private Vector3 lastKnownPosition;
        private string previousZoneId;
        private IEncounter activeEncounter;
        private int lastCheckedDay;

        public EncounterHandler(ZoneSystem zoneSystem, EncounterRegistry encounterRegistry, EncounterPrefabs encounterPrefabs, AdventureData adventureData) {
            this.zoneSystem = zoneSystem;
            this.encounterRegistry = encounterRegistry;
            this.adventureData = adventureData;
            encounterView = new EncounterView(encounterPrefabs);
            encounterView.OptionSelected += OnOptionSelected;
            lastCheckedDay = adventureData.currentDay;
        }

        public bool AskForRandomEncounter(ZoneContext zoneContext, string encounterTableId, out IEncounter encounter) {
            return ResolveEncounter(zoneContext, encounterTableId, out encounter);
        }

        public bool AskForRandomEncounterOfType(ZoneContext zoneContext, string encounterTableId, out IEncounter encounter, IEncounterKind encounterKind) {
            return ResolveEncounter(zoneContext, encounterTableId, out encounter, encounterKind);
        }

        public bool AskForEncounter(string encounterId, out IEncounter encounter) {
            return encounterRegistry.GetEncounters().TryGetValue(encounterId, out encounter);
        }

        private bool ResolveEncounter(ZoneContext zoneContext, string encounterTableId, out IEncounter encounter, IEncounterKind encounterKind = null) {
            encounter = null;
            if(zoneContext.isSafe) {
                return false;
            }

            var randomChance = UnityEngine.Random.Range(0f, 1f);
            var shouldTrigger = randomChance <= zoneContext.finalEncounterChance;

            if(!shouldTrigger) {
                Debug.Log($"Rolled for encounter '{encounterTableId}': {randomChance:F2}/{zoneContext.finalEncounterChance:F2} -> none");
                return false;
            }

            var filter = new Predicate<IEncounter>(e => e != null && !IsAlreadyCompleted(e));

            if(encounterKind == null) {
                encounter = encounterRegistry.GetRandomEncounter(filter, encounterTableId);
            }
            else {
                var table = encounterRegistry.GetEncounterTable(encounterTableId);
                if(table == null) {
                    return false;
                }

                for(var attempt = 0; attempt <= table.encounters.Count; attempt++) {
                    encounter = encounterRegistry.GetRandomEncounter(encounterTableId, encounterKind);
                    if(encounter == null || !IsAlreadyCompleted(encounter)) {
                        break;
                    }
                }

                if(encounter != null && IsAlreadyCompleted(encounter)) {
                    return false;
                }
            }

            var name = encounter?.EncounterDefinition?.name ?? "none";
            Debug.Log($"Rolled for encounter '{encounterTableId}': {randomChance:F2}/{zoneContext.finalEncounterChance:F2} -> {name}");
            return encounter != null;
        }

        private bool IsAlreadyCompleted(IEncounter encounter) {
            var def = encounter?.EncounterDefinition;
            if(def == null || string.IsNullOrEmpty(def.id)) {
                return false;
            }

            // no properties → defaults to repeatable (true). So can't be "already completed".
            if(encounter.EncounterProperties is not { isRepeatable: false }) {
                return false;
            }

            return adventureData.completedEncounterIds.Contains(def.id);
        }

        private void RecordEncounterCompleted(IEncounter encounter) {
            var def = encounter?.EncounterDefinition;
            if(def == null || string.IsNullOrEmpty(def.id)) {
                return;
            }

            // only record non-repeatable encounters that have explicit properties
            if(encounter.EncounterProperties is not { isRepeatable: false }) {
                return;
            }

            if(!adventureData.completedEncounterIds.Contains(def.id)) {
                adventureData.completedEncounterIds.Add(def.id);
                Debug.Log($"Recorded non-repeatable encounter: {def.id}");
            }
        }

        private void TrackZoneTransition(Vector3 position) {
            var zoneContext = zoneSystem.QueryZone(position);
            var currentZoneId = zoneContext.resolvedZoneId;

            if(currentZoneId != previousZoneId) {
                if(!string.IsNullOrEmpty(currentZoneId)) {
                    Debug.Log($"Entered zone: {currentZoneId} (encounter: {zoneContext.encounterTableId}, safe: {zoneContext.isSafe})");
                }
                else if(!string.IsNullOrEmpty(previousZoneId)) {
                    Debug.Log($"Left zone: {previousZoneId}");
                }
                previousZoneId = currentZoneId;
            }
        }

        private void DailyEncounterCheck(Vector3 position) {
            var zoneContext = zoneSystem.QueryZone(position);
            if(string.IsNullOrEmpty(zoneContext.resolvedZoneId) || zoneContext.isSafe) {
                return;
            }
            if(ResolveEncounter(zoneContext, zoneContext.encounterTableId, out var encounter)) {
                TriggerEncounter(encounter);
            }
        }

        private void TriggerEncounter(IEncounter encounter) {
            switch(encounter.EncounterDefinition.Kind) {
                case CombatKind:
                    return;
                default:
                    activeEncounter = encounter;
                    adventureData.isEncounterActive = true;
                    encounterView?.SetCurrentEncounter(encounter);
                    encounterView?.Show();
                    break;
            }
        }

        private void FinishEncounter() {
            RecordEncounterCompleted(activeEncounter);
            encounterView?.Hide();
            activeEncounter = null;
            adventureData.isEncounterActive = false;
        }

        private void OnOptionSelected(int optionIndex) {
            if(activeEncounter == null) {
                return;
            }

            var options = activeEncounter.EncounterDialogOptionSet?.options;
            if(options == null || optionIndex < 0 || optionIndex >= options.Count) {
                FinishEncounter();
                return;
            }

            ResolveOption(activeEncounter, options[optionIndex]);
        }

        private void ResolveOption(IEncounter encounter, EncounterDialogOption option) {
            if(option?.events == null) {
                FinishEncounter();
                return;
            }

            for(var i = 0; i < option.events.Count; i++) {
                var encounterEvent = option.events[i];
                if(encounterEvent == null) {
                    continue;
                }
                switch(encounterEvent) {
                    case ChainToEncounterEvent chain:
                        if(AskForEncounter(chain.nextEncounterId, out var next)) {
                            RecordEncounterCompleted(activeEncounter);
                            TriggerEncounter(next);
                            return;
                        }
                        break;
                    case LogEvent log:
                        Debug.Log($"[Encounter '{encounter.EncounterDefinition.id}'] {log.message}");
                        break;
                    case StartCombatEvent _:
                    case GiveRewardEvent _:
                        Debug.Log($"[Encounter] unhandled event {encounterEvent.GetType().Name}");
                        break;
                }
            }

            FinishEncounter();
        }

        public void CheckForEncounters(Vector3 position) {
            lastKnownPosition = position;
            TrackZoneTransition(position);
        }

        public void Tick() {
            if(adventureData.currentDay != lastCheckedDay) {
                lastCheckedDay = adventureData.currentDay;
                DailyEncounterCheck(lastKnownPosition);
            }
        }

        public void Dispose() {
            if(encounterView != null) {
                encounterView.OptionSelected -= OnOptionSelected;
                encounterView.Dispose();
            }
            activeEncounter = null;
            adventureData.isEncounterActive = false;
        }
    }
}
