using Jovian.InGameLogging;
using Jovian.InGameLogging.UI;
using Jovian.SaveSystem;
using Nox.Core;
using Nox.Game;
using Nox.Game.UI;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using ZLinq;
using Attribute = Nox.Game.Attribute;
using PlayMode = Nox.Core.PlayMode;

namespace Nox.UI {
    public class CharacterCreationView : IGameLifecycle, IMenuView {
        public ISaveSystem SaveSystem { get; }

        private readonly CharacterCreationReference characterCreationReference;
        private readonly MenuGameStateData menuGameStateData;
        private readonly GameDataState gameDataState;
        private readonly PartySettings partySettings;
        private readonly ICharacterSystems characterSystems;
        private readonly PortraitsHolder portraitsHolder;
        private readonly StarterCharacterSettings starterCharacterSettings;

        // Logger
        private GameLogView gameLogView;
        private InGameLogger inGameLogger;

        // Working state
        private CharacterRace selectedRace;
        private CharacterClass selectedClass;
        private int currentPortraitIndex;
        private int remainingPoints;
        private readonly int[] allocatedPoints = new int[4]; // Might, Reflex, Knowledge, Perception (AttributeType 1-4)
        private int previousHealth;
        private int previousMana;

        // Modifier source tracking
        private PerksData racialPerks = new();
        private ModifiersData racialModifiers = new();
        private PerksData classPerks = new();
        private ModifiersData classModifiers = new();
        private readonly List<PerkDefinition> playerPerks = new();
        private List<IPerk> availablePerks = new();

        // Computed state
        private EntityAttributes workingAttributes;
        private EntityStats workingStats;

        // Output
        private List<CharacterCreationRequest> characterCreationRequests;
        private Action canStartCheck;

        // Back confirmation (null until popup is implemented)
        private Action confirmBackAction;

        public CharacterCreationView(CharacterCreationReference characterCreationReference,
            MenuGameStateData menuGameStateData,
            ISaveSystem saveSystem,
            GameDataState gameDataState,
            PartySettings partySettings,
            ICharacterSystems characterSystems,
            PortraitsHolder portraitsHolder,
            StarterCharacterSettings starterCharacterSettings) {
            SaveSystem = saveSystem;
            this.characterCreationReference = characterCreationReference;
            this.menuGameStateData = menuGameStateData;
            this.gameDataState = gameDataState;
            this.partySettings = partySettings;
            this.characterSystems = characterSystems;
            this.portraitsHolder = portraitsHolder;
            this.starterCharacterSettings = starterCharacterSettings;
        }

        public void Initialize() {
            // Logger
            var store = new GameLogStore(500);
            gameLogView = characterCreationReference.gameLogView;
            gameLogView.Initialize(store);
            inGameLogger = new InGameLogger(store, LogChannel.CharacterCreation);
            inGameLogger.Enable();

            // Start Game button
            canStartCheck = () => {
                var canStart = characterCreationRequests is { Count: > 0 };
                characterCreationReference.startGameButton.interactable = canStart;
            };
            characterCreationReference.startGameButton.onClick.AddListener(() => {
                if(characterCreationRequests == null || characterCreationRequests.Count == 0) {
                    inGameLogger.Log("You must accept your character before starting the game.", "#FF4444");
                    return;
                }
                Hide();
                menuGameStateData.startGameRequests?.Invoke(PlayMode.Adventure);
            });

            // Back buttons with popup check
            characterCreationReference.backButton.onClick.AddListener(OnBackClicked);
            characterCreationReference.backButtonCenter.onClick.AddListener(OnBackClicked);

            // Accept button
            characterCreationReference.acceptButton.onClick.AddListener(OnAcceptClicked);

            // Race dropdown
            PopulateEnumDropdown<CharacterRace>(characterCreationReference.raceDropdown);
            characterCreationReference.raceDropdown.onValueChanged.AddListener(OnRaceChanged);

            // Class dropdown
            PopulateEnumDropdown<CharacterClass>(characterCreationReference.classDropdown);
            characterCreationReference.classDropdown.onValueChanged.AddListener(OnClassChanged);

            // Perks dropdown
            PopulatePerksDropdown();
            characterCreationReference.perksDropdown.onValueChanged.AddListener(OnPerkSelected);

            // Attribute +/- buttons
            var attrTypes = new[] { AttributeType.Might, AttributeType.Reflex, AttributeType.Knowledge, AttributeType.Perception };
            var attrRefs = characterCreationReference.attributeReference;
            for(int i = 0; i < attrRefs.Length && i < attrTypes.Length; i++) {
                var type = attrTypes[i];
                attrRefs[i].attributeName.text = type.ToString();
                attrRefs[i].addPointsButton.onClick.AddListener(() => OnAttributeAdd(type));
                attrRefs[i].removePointsButton.onClick.AddListener(() => OnAttributeRemove(type));
            }

            // Portrait navigation
            currentPortraitIndex = 0;
            if(portraitsHolder != null && portraitsHolder.portraits.Length > 0) {
                characterCreationReference.portraitImage.sprite = portraitsHolder.portraits[0];
            }
            characterCreationReference.portraitSelectionLeftButton.onClick.AddListener(OnPortraitLeft);
            characterCreationReference.portraitSelectionRightButton.onClick.AddListener(OnPortraitRight);

            // Initial state
            selectedRace = CharacterRace.Human;
            selectedClass = CharacterClass.Warrior;
            characterCreationReference.raceDropdown.SetValueWithoutNotify(0);
            characterCreationReference.classDropdown.SetValueWithoutNotify(0);
            ResetWorkingState();
        }

        // --- Dropdown helpers ---

        private void PopulateEnumDropdown<T>(TMP_Dropdown dropdown) where T : Enum {
            dropdown.ClearOptions();
            var options = new List<string>();
            foreach(T value in Enum.GetValues(typeof(T))) {
                if(Convert.ToInt32(value) == 0) {
                    continue; // skip None
                }
                options.Add(value.ToString());
            }
            dropdown.AddOptions(options);
        }

        private void PopulatePerksDropdown() {
            var dropdown = characterCreationReference.perksDropdown;
            dropdown.ClearOptions();
            availablePerks = new List<IPerk>(characterSystems.PerkFactory.GetAll());

            var options = new List<string> { "Select a perk..." };
            foreach(var perk in availablePerks) {
                options.Add(perk.Name);
            }
            dropdown.AddOptions(options);
            dropdown.SetValueWithoutNotify(0);
        }

        // --- State management ---

        private void ResetWorkingState() {
            Array.Clear(allocatedPoints, 0, allocatedPoints.Length);
            racialPerks = new PerksData();
            racialModifiers = new ModifiersData();
            classPerks = new PerksData();
            classModifiers = new ModifiersData();
            playerPerks.Clear();

            ApplyRacialBonuses();
            ApplyClassBonuses();
            UpdateRemainingPoints();
            RecalculateAll();

            // Initialize previous values so first change doesn't log a delta from 0
            previousHealth = workingStats.GetValue(StatType.Health);
            previousMana = workingStats.GetValue(StatType.Mana);
        }

        private void ApplyRacialBonuses() {
            racialPerks = new PerksData();
            racialModifiers = new ModifiersData();
            var bonuses = starterCharacterSettings.racialBonuses;
            if(bonuses == null) {
                return;
            }
            foreach(var rb in bonuses) {
                if(rb.race == selectedRace) {
                    racialPerks = rb.startingPerks ?? new PerksData();
                    racialModifiers = rb.permanentModifiers ?? new ModifiersData();
                    break;
                }
            }
        }

        private void ApplyClassBonuses() {
            classPerks = new PerksData();
            classModifiers = new ModifiersData();
            var bonuses = starterCharacterSettings.classBonuses;
            if(bonuses == null) {
                return;
            }
            foreach(var cb in bonuses) {
                if(cb.@class == selectedClass) {
                    classPerks = cb.startingPerks ?? new PerksData();
                    classModifiers = cb.permanentModifiers ?? new ModifiersData();
                    break;
                }
            }
        }

        private void UpdateRemainingPoints() {
            var totalPoints = 0;
            if(starterCharacterSettings.distributionPointsPerClass != null) {
                foreach(var dpc in starterCharacterSettings.distributionPointsPerClass) {
                    if(dpc.@class == selectedClass) {
                        totalPoints = dpc.points;
                        break;
                    }
                }
            }
            var spent = 0;
            for(int i = 0; i < allocatedPoints.Length; i++) {
                spent += allocatedPoints[i];
            }
            remainingPoints = totalPoints - spent;
        }

        // --- Core calculation ---

        private void RecalculateAll() {
            // 1. Start from default attributes + player allocations
            var baseAttrs = starterCharacterSettings.defaultEntityAttributes;
            var attrTypes = new[] { AttributeType.Might, AttributeType.Reflex, AttributeType.Knowledge, AttributeType.Perception };
            var finalAttrs = new Attribute[attrTypes.Length];
            for(int i = 0; i < attrTypes.Length; i++) {
                var baseVal = baseAttrs.GetValue(attrTypes[i]);
                finalAttrs[i] = new Attribute(attrTypes[i], baseVal + allocatedPoints[i]);
            }
            workingAttributes = new EntityAttributes { attributes = finalAttrs };

            // 2. Build combined perks and modifiers (defaults + racial + class + player)
            // Racial/class attribute and stat bonuses flow through permanentModifiers
            var combinedPerks = BuildCombinedPerks();
            var combinedModifiers = BuildCombinedModifiers();

            // 3. Build temp entity for modifier collection
            var tempEntity = new CharacterCreationRequest {
                Id = Guid.NewGuid(),
                Race = selectedRace,
                Class = selectedClass,
                Role = CharacterRole.Protagonist,
                Attributes = workingAttributes,
                Perks = combinedPerks,
                Modifiers = combinedModifiers
            };

            // 4. Resolve attributes through modifiers (racial/class attribute bonuses come via modifiers)
            var resolver = characterSystems.ModifierResolver;
            var resolvedAttrs = new Attribute[attrTypes.Length];
            for(int i = 0; i < attrTypes.Length; i++) {
                var mods = resolver.CollectModifiers(tempEntity, attrTypes[i]);
                resolvedAttrs[i] = new Attribute(attrTypes[i], resolver.Resolve(finalAttrs[i].value, mods, tempEntity));
            }
            workingAttributes = new EntityAttributes { attributes = resolvedAttrs };
            tempEntity.Attributes = workingAttributes;

            // 5. Calculate stats through modifiers (racial/class stat bonuses come via modifiers)
            var baseStats = starterCharacterSettings.defaultEntityStats;
            var statTypes = new[] { StatType.Health, StatType.Mana, StatType.Level, StatType.Experience };
            var resolvedStats = new Stat[statTypes.Length];
            for(int i = 0; i < statTypes.Length; i++) {
                var baseVal = baseStats.GetValue(statTypes[i]);
                var mods = resolver.CollectModifiers(tempEntity, statTypes[i]);
                resolvedStats[i] = new Stat(statTypes[i], resolver.Resolve(baseVal, mods, tempEntity));
            }
            workingStats = new EntityStats { stats = resolvedStats };

            // 9. Update UI
            UpdateAttributeUI();
            UpdateStatUI();
            UpdatePointsDisplay();

            // 10. Log stat deltas
            var newHealth = workingStats.GetValue(StatType.Health);
            var newMana = workingStats.GetValue(StatType.Mana);
            if(newHealth != previousHealth && previousHealth != 0) {
                var delta = newHealth - previousHealth;
                var sign = delta > 0 ? "+" : "";
                inGameLogger.Log($"Health: {previousHealth} -> {newHealth} ({sign}{delta})", "#87CEEB");
            }
            if(newMana != previousMana && previousMana != 0) {
                var delta = newMana - previousMana;
                var sign = delta > 0 ? "+" : "";
                inGameLogger.Log($"Mana: {previousMana} -> {newMana} ({sign}{delta})", "#FFFF99");
            }
            previousHealth = newHealth;
            previousMana = newMana;
        }

        private PerksData BuildCombinedPerks() {
            // Start with defaults, add racial/class/player perks (deduplicate by Id)
            var combined = new PerksData { perks = new List<PerkDefinition>() };
            var seenIds = new HashSet<Guid>();

            void AddPerks(PerksData source) {
                if(source?.perks == null) {
                    return;
                }
                foreach(var perk in source.perks) {
                    if(perk != null && seenIds.Add(perk.Id)) {
                        combined.perks.Add(perk);
                    }
                }
            }

            AddPerks(starterCharacterSettings.defaultPerksData);
            AddPerks(racialPerks);
            AddPerks(classPerks);
            foreach(var perk in playerPerks) {
                if(perk != null && seenIds.Add(perk.Id)) {
                    combined.perks.Add(perk);
                }
            }
            return combined;
        }

        private ModifiersData BuildCombinedModifiers() {
            // Start with defaults. Racial/class modifiers override defaults that target the same
            // thing, but ONLY if the override's requirements are currently met. If requirements
            // are not met, the default stays and the override is still added — the resolver will
            // skip the unqualified override at resolution time, leaving the default active.
            var combined = new ModifiersData { modifiers = new List<ModifierDefinition>() };

            // Seed with defaults
            if(starterCharacterSettings.defaultModifiersData?.modifiers != null) {
                combined.modifiers.AddRange(starterCharacterSettings.defaultModifiersData.modifiers);
            }

            // Override with racial modifiers
            OverrideModifiers(combined, racialModifiers, workingAttributes);

            // Override with class modifiers
            OverrideModifiers(combined, classModifiers, workingAttributes);

            return combined;
        }

        private static void OverrideModifiers(ModifiersData combined, ModifiersData overrides, EntityAttributes currentAttributes) {
            if(overrides?.modifiers == null) {
                return;
            }
            foreach(var mod in overrides.modifiers) {
                if(mod?.Target == null) {
                    continue;
                }
                // Only remove the default if the override's requirements are currently met.
                // Both are added regardless — the resolver skips unqualified modifiers at
                // resolution time, so if requirements aren't met, the default still applies.
                var requirementsMet = AreRequirementsMet(mod, currentAttributes);
                if(requirementsMet) {
                    for(int i = combined.modifiers.Count - 1; i >= 0; i--) {
                        var existing = combined.modifiers[i];
                        if(existing?.Target != null && TargetsMatch(existing.Target, mod.Target)) {
                            combined.modifiers.RemoveAt(i);
                        }
                    }
                }
                combined.modifiers.Add(mod);
            }
        }

        private static bool AreRequirementsMet(ModifierDefinition mod, EntityAttributes attributes) {
            if(mod.Requirements == null || mod.Requirements.Count == 0) {
                return true;
            }
            foreach(var req in mod.Requirements) {
                if(!req.IsMet(attributes)) {
                    return false;
                }
            }
            return true;
        }

        private static bool TargetsMatch(ModifierTarget a, ModifierTarget b) {
            if(a.Type != b.Type) {
                return false;
            }
            return a.Type switch {
                ModifierTargetType.Attribute => a.AttributeType == b.AttributeType,
                ModifierTargetType.Stat => a.StatType == b.StatType,
                ModifierTargetType.CombatScore => a.CombatScoreType == b.CombatScoreType,
                _ => false
            };
        }

        // --- UI updates ---

        private void UpdateAttributeUI() {
            var attrTypes = new[] { AttributeType.Might, AttributeType.Reflex, AttributeType.Knowledge, AttributeType.Perception };
            var attrRefs = characterCreationReference.attributeReference;
            for(int i = 0; i < attrRefs.Length && i < attrTypes.Length; i++) {
                attrRefs[i].attributeValue.text = workingAttributes.GetValue(attrTypes[i]).ToString();
                attrRefs[i].addPointsButton.interactable = remainingPoints > 0;
                attrRefs[i].removePointsButton.interactable = allocatedPoints[i] > 0;
            }
        }

        private void UpdateStatUI() {
            var statTypes = new[] { StatType.Health, StatType.Mana };
            var statRefs = characterCreationReference.statReference;
            for(int i = 0; i < statRefs.Length && i < statTypes.Length; i++) {
                var value = workingStats.GetValue(statTypes[i]);
                statRefs[i].statName.text = statTypes[i].ToString();
                statRefs[i].statValue.text = value.ToString();
                statRefs[i].statBar.fillAmount = Mathf.Clamp01(value / 200f);
            }
        }

        private void UpdatePointsDisplay() {
            characterCreationReference.pointsToDistribute.text = remainingPoints.ToString();
        }

        // --- Event handlers ---

        private void OnRaceChanged(int index) {
            selectedRace = (CharacterRace)(index + 1);
            Array.Clear(allocatedPoints, 0, allocatedPoints.Length);
            ApplyRacialBonuses();
            UpdateRemainingPoints();
            RecalculateAll();
            inGameLogger.Log($"Race changed to {selectedRace}");
        }

        private void OnClassChanged(int index) {
            selectedClass = (CharacterClass)(index + 1);
            Array.Clear(allocatedPoints, 0, allocatedPoints.Length);
            ApplyClassBonuses();
            UpdateRemainingPoints();
            RecalculateAll();
            inGameLogger.Log($"Class changed to {selectedClass}");
        }

        private void OnPerkSelected(int index) {
            if(index <= 0 || index > availablePerks.Count) {
                return; // "Select a perk..." placeholder
            }

            var perkIndex = index - 1; // offset for placeholder
            var perk = availablePerks[perkIndex];
            playerPerks.Add(new PerkDefinition {
                Id = perk.Id,
                Name = perk.Name,
                Modifiers = perk.Modifiers
            });
            availablePerks.RemoveAt(perkIndex);
            PopulatePerksDropdown();
            RecalculateAll();
            inGameLogger.Log($"Perk added: {perk.Name}");
        }

        private void OnAttributeAdd(AttributeType type) {
            if(remainingPoints <= 0) {
                return;
            }
            var idx = (int)type - 1;
            allocatedPoints[idx]++;
            remainingPoints--;
            RecalculateAll();
        }

        private void OnAttributeRemove(AttributeType type) {
            var idx = (int)type - 1;
            if(allocatedPoints[idx] <= 0) {
                return;
            }
            allocatedPoints[idx]--;
            remainingPoints++;
            RecalculateAll();
        }

        private void OnPortraitLeft() {
            if(portraitsHolder == null || portraitsHolder.portraits.Length == 0) {
                return;
            }
            currentPortraitIndex--;
            if(currentPortraitIndex < 0) {
                currentPortraitIndex = portraitsHolder.portraits.Length - 1;
            }
            characterCreationReference.portraitImage.sprite = portraitsHolder.portraits[currentPortraitIndex];
        }

        private void OnPortraitRight() {
            if(portraitsHolder == null || portraitsHolder.portraits.Length == 0) {
                return;
            }
            currentPortraitIndex++;
            if(currentPortraitIndex >= portraitsHolder.portraits.Length) {
                currentPortraitIndex = 0;
            }
            characterCreationReference.portraitImage.sprite = portraitsHolder.portraits[currentPortraitIndex];
        }

        private void OnBackClicked() {
            if(confirmBackAction != null) {
                confirmBackAction();
                return;
            }
            Hide();
        }

        // --- Accept ---

        private void OnAcceptClicked() {
            var errors = new List<string>();
            if(selectedRace == CharacterRace.None) {
                errors.Add("Race must be selected");
            }
            if(selectedClass == CharacterClass.None) {
                errors.Add("Class must be selected");
            }
            if(remainingPoints > 0) {
                errors.Add($"{remainingPoints} distribution points remaining");
            }
            var characterName = characterCreationReference.nameInputField.text;
            if(string.IsNullOrWhiteSpace(characterName)) {
                errors.Add("Name cannot be empty");
            }

            if(errors.Count > 0) {
                foreach(var error in errors) {
                    inGameLogger.Log(error, "#FF4444");
                }
                return;
            }

            var request = new CharacterCreationRequest {
                Id = Guid.NewGuid(),
                Name = characterName,
                Race = selectedRace,
                Class = selectedClass,
                Role = CharacterRole.Protagonist,
                PortraitIndex = currentPortraitIndex,
                Attributes = workingAttributes,
                Stats = workingStats,
                Perks = BuildCombinedPerks(),
                Modifiers = BuildCombinedModifiers()
            };

            characterCreationRequests = new List<CharacterCreationRequest> { request };

            // Log full breakdown
            inGameLogger.Log("--- Character Accepted ---");
            inGameLogger.Log($"Name: {request.Name}", "#FFBF00");
            inGameLogger.Log($"Race: {request.Race}");
            inGameLogger.Log($"Class: {request.Class}");
            inGameLogger.Log($"Portrait: #{request.PortraitIndex}");
            inGameLogger.Log($"{request.Attributes}");
            inGameLogger.Log($"{request.Stats}");
            if(request.Perks?.perks != null && request.Perks.perks.Count > 0) {
                foreach(var perk in request.Perks.perks) {
                    inGameLogger.Log($"Perk: {perk.Name}");
                }
            }

            CreateParty();
            canStartCheck.Invoke();
        }

        private void CreateParty() {
            var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory, characterCreationRequests, partySettings);
            var party = partyCreatorModel.CreatePartyForNewRun();
            gameDataState.ActiveParty = party;
        }

        // --- Lifecycle ---

        public void Tick() {
        }

        public void Show() {
            characterCreationReference.gameObject.SetActive(true);
        }

        public void Hide() {
            characterCreationReference.gameObject.SetActive(false);
        }

        public void Dispose() {
            inGameLogger.Disable();
        }
    }
}
