using System.Collections.Generic;
using UnityEngine;
using VContainer;
using YachtDice.Categories;
using YachtDice.Dice;
using YachtDice.Game;
using YachtDice.Scoring;
using YachtDice.Economy;
using YachtDice.Shop;
using YachtDice.Inventory;
using YachtDice.Persistence;
using YachtDice.Player;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Runtime;

namespace YachtDice.UI
{
    public class GameController : MonoBehaviour
    {
        [Header("Views")]
        [SerializeField] private ScoreCardView scoreCardView;
        [SerializeField] private DicePanelView dicePanelView;
        [SerializeField] private GameInfoView gameInfoView;

        [Header("Settings")]
        [SerializeField] private int maxRollsPerTurn = 3;

        private const int UpperBonusThreshold = 63;
        private const int UpperBonusValue = 35;

        private GameManager gameManager;
        private ScoringSystem scoringSystem;
        private DiceManager diceManager;
        private CurrencyBank currencyBank;
        private ShopController shopController;
        private InventoryController inventoryController;
        private ModifierRegistry modifierRegistry;
        private CategoryCatalog categoryCatalog;
        private ModifierCatalog modifierCatalog;
        private DiceCatalog diceCatalog;
        private ShopModel shopModel;
        private PlayerModel playerModel;

        [Inject]
        public void Construct(
            GameManager gameManager,
            ScoringSystem scoringSystem,
            DiceManager diceManager,
            CurrencyBank currencyBank,
            ShopController shopController,
            InventoryController inventoryController,
            ModifierRegistry modifierRegistry,
            CategoryCatalog categoryCatalog,
            ModifierCatalog modifierCatalog,
            DiceCatalog diceCatalog,
            ShopModel shopModel,
            PlayerModel playerModel)
        {
            this.gameManager = gameManager;
            this.scoringSystem = scoringSystem;
            this.diceManager = diceManager;
            this.currencyBank = currencyBank;
            this.shopController = shopController;
            this.inventoryController = inventoryController;
            this.modifierRegistry = modifierRegistry;
            this.categoryCatalog = categoryCatalog;
            this.modifierCatalog = modifierCatalog;
            this.diceCatalog = diceCatalog;
            this.shopModel = shopModel;
            this.playerModel = playerModel;
        }

        // ── Lifecycle ──────────────────────────────────────────────

        private void Start()
        {
            // Model → Controller
            gameManager.OnTurnStarted += HandleTurnStarted;
            gameManager.OnRollComplete += HandleRollComplete;
            gameManager.OnScored += HandleScored;
            gameManager.OnGameOver += HandleGameOver;
            diceManager.OnDieSettled += HandleDieSettled;

            // View → Controller
            scoreCardView.OnCategorySelected += HandleCategorySelected;
            dicePanelView.OnRollClicked += HandleRollClicked;
            dicePanelView.OnDiceToggled += HandleDiceToggled;
            gameInfoView.OnNewGameClicked += HandleNewGameClicked;
            gameInfoView.OnShopClicked += HandleShopClicked;
            gameInfoView.OnInventoryClicked += HandleInventoryClicked;

            // Currency & Player state
            currencyBank.OnBalanceChanged += HandleCurrencyChanged;
            playerModel.OnChanged += HandlePlayerChangedForSave;

            // Initialize
            scoreCardView.Initialize(categoryCatalog);
            LoadSaveData();
            gameInfoView.SetCurrencyText(currencyBank.Balance);

            // Start the game after all subscriptions are in place
            gameManager.StartNewGame();
        }

        private void OnDestroy()
        {
            gameManager.OnTurnStarted -= HandleTurnStarted;
            gameManager.OnRollComplete -= HandleRollComplete;
            gameManager.OnScored -= HandleScored;
            gameManager.OnGameOver -= HandleGameOver;
            diceManager.OnDieSettled -= HandleDieSettled;

            scoreCardView.OnCategorySelected -= HandleCategorySelected;
            dicePanelView.OnRollClicked -= HandleRollClicked;
            dicePanelView.OnDiceToggled -= HandleDiceToggled;
            gameInfoView.OnNewGameClicked -= HandleNewGameClicked;
            gameInfoView.OnShopClicked -= HandleShopClicked;
            gameInfoView.OnInventoryClicked -= HandleInventoryClicked;

            currencyBank.OnBalanceChanged -= HandleCurrencyChanged;

            if (playerModel != null)
                playerModel.OnChanged -= HandlePlayerChangedForSave;
        }

        // ── Save / Load ─────────────────────────────────────────

        private void LoadSaveData()
        {
            SaveData save = SaveSystem.Load();

            if (save.Currency > 0)
                currencyBank.SetBalance(save.Currency);

            if (modifierCatalog != null && save.OwnedModifiers.Count > 0)
            {
                var entries = new List<ModifierSaveEntry>();
                var permanentIds = new HashSet<string>();

                for (int i = 0; i < save.OwnedModifiers.Count; i++)
                {
                    var oldEntry = save.OwnedModifiers[i];
                    var def = modifierCatalog.FindById(oldEntry.ModifierId);

                    if (def == null)
                    {
                        Debug.LogWarning($"Modifier '{oldEntry.ModifierId}' not found in catalog, skipping.");
                        continue;
                    }

                    entries.Add(new ModifierSaveEntry
                    {
                        ModifierId = oldEntry.ModifierId,
                        IsActive = oldEntry.IsActive,
                        RemainingUses = oldEntry.RemainingUses,
                        Stacks = oldEntry.Stacks,
                        CustomState = oldEntry.CustomState,
                    });

                    if (!def.HasLimitedUses)
                        permanentIds.Add(def.Id);
                }

                modifierRegistry.LoadSaveData(entries, modifierCatalog);
                shopModel.LoadPurchasedPermanentIds(permanentIds);
            }

            if (diceCatalog != null && save.OwnedDiceIds != null && save.OwnedDiceIds.Count > 0)
            {
                playerModel.Dice.LoadSaveData(save.OwnedDiceIds, diceCatalog);

                var dicePermIds = new HashSet<string>(save.OwnedDiceIds);
                var existingIds = shopModel.GetPurchasedPermanentIds();
                foreach (var id in dicePermIds)
                    existingIds.Add(id);
                shopModel.LoadPurchasedPermanentIds(existingIds);
            }
        }

        private void PerformSave()
        {
            var save = new SaveData
            {
                Currency = currencyBank.Balance,
                OwnedDiceIds = playerModel.Dice.GetSaveData(),
            };

            var entries = modifierRegistry.GetSaveData();
            for (int i = 0; i < entries.Count; i++)
            {
                save.OwnedModifiers.Add(entries[i]);
            }

            SaveSystem.Save(save);
        }

        // ── Model Event Handlers ──────────────────────────────────

        private void HandleTurnStarted(int turn)
        {
            int totalCategoryCount = categoryCatalog.Count;
            gameInfoView.SetTurnText(turn, totalCategoryCount);
            dicePanelView.ResetForNewTurn();
            dicePanelView.SetRollButtonState(true, 0, maxRollsPerTurn);
            scoreCardView.ClearAllPreviews();
        }

        private void HandleRollComplete(int rollNumber)
        {
            bool canRollAgain = gameManager.CanRoll;
            dicePanelView.SetRollButtonState(canRollAgain, rollNumber, maxRollsPerTurn);
            dicePanelView.SetDiceInteractable(true);

            int[] values = diceManager.GetCurrentValues();
            dicePanelView.SetAllDiceValues(values);

            UpdatePreviewScores();
        }

        private void HandleDieSettled(int index, int value)
        {
            dicePanelView.SetDieValue(index, value);
        }

        private void HandleScored(CategoryDefinition category, int finalScore)
        {
            scoreCardView.SetCategoryScored(category, finalScore);
            UpdateTotalDisplay();
            PerformSave();
        }

        private void HandleGameOver(int totalScore)
        {
            dicePanelView.SetRollButtonState(false, maxRollsPerTurn, maxRollsPerTurn);
            dicePanelView.SetDiceInteractable(false);
            scoreCardView.SetAllInteractable(false);

            int displayTotal = CalculateDisplayTotal();
            gameInfoView.ShowGameOver(displayTotal);
            PerformSave();
        }

        // ── View Event Handlers ───────────────────────────────────

        private void HandleRollClicked()
        {
            if (!gameManager.CanRoll) return;

            dicePanelView.SetRollButtonState(false, gameManager.CurrentRoll, maxRollsPerTurn);
            dicePanelView.SetDiceInteractable(false);
            scoreCardView.SetAllInteractable(false);

            gameManager.Roll();
        }

        private void HandleDiceToggled(int index)
        {
            if (gameManager.CurrentRoll == 0) return;
            if (diceManager.IsAnyRolling) return;

            gameManager.ToggleDiceLock(index);

            bool isLocked = diceManager.IsLocked(index);
            dicePanelView.SetDieLocked(index, isLocked);
        }

        private void HandleCategorySelected(CategoryDefinition category)
        {
            if (!gameManager.CanScore) return;
            if (scoringSystem.IsCategoryUsed(category)) return;

            gameManager.ScoreInCategory(category);
        }

        private void HandleNewGameClicked()
        {
            gameInfoView.HideGameOver();
            scoreCardView.ResetAll();
            dicePanelView.ResetForNewGame();
            gameManager.StartNewGame();
        }

        private void HandleShopClicked()
        {
            shopController.ToggleVisibility();
        }

        private void HandleInventoryClicked()
        {
            inventoryController.ToggleVisibility();
        }

        private void HandleCurrencyChanged(int newBalance)
        {
            gameInfoView.SetCurrencyText(newBalance);
        }

        private void HandlePlayerChangedForSave()
        {
            PerformSave();
        }

        // ── Helpers ────────────────────────────────────────────────

        private void UpdatePreviewScores()
        {
            var dice = diceManager.GetDice();
            var previews = new Dictionary<CategoryDefinition, int>();
            var allCategories = categoryCatalog.All;

            for (int i = 0; i < allCategories.Count; i++)
            {
                var cat = allCategories[i];
                if (scoringSystem.IsCategoryUsed(cat)) continue;

                ScoreResult result = scoringSystem.PreviewScore(dice, cat);
                previews[cat] = result.FinalScore;
            }

            scoreCardView.UpdatePreviews(previews);
        }

        private void UpdateTotalDisplay()
        {
            int upperSum = CalculateUpperSum();
            bool hasBonus = upperSum >= UpperBonusThreshold;
            int displayTotal = CalculateDisplayTotal();

            scoreCardView.UpdateTotalDisplay(displayTotal, upperSum, hasBonus);
        }

        private int CalculateUpperSum()
        {
            int upperSum = 0;
            var allCategories = categoryCatalog.All;

            for (int i = 0; i < allCategories.Count; i++)
            {
                if (!allCategories[i].IsUpperSection) continue;

                int catScore = scoringSystem.GetCategoryScore(allCategories[i]);
                if (catScore >= 0) upperSum += catScore;
            }

            return upperSum;
        }

        private int CalculateDisplayTotal()
        {
            int total = scoringSystem.TotalScore;
            int upperSum = CalculateUpperSum();

            if (upperSum >= UpperBonusThreshold)
                total += UpperBonusValue;

            return total;
        }
    }
}
