import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';

import '../game/models.dart';
import '../game/data.dart';
import '../game/enums.dart';
import '../game/save_manager.dart';
import '../game/config.dart';
import '../game/logic.dart';
import 'shop_provider.dart';

class BattleProvider with ChangeNotifier {
  static final Duration _turnEffectVisualDelay =
      AnimationConfig.floatingTextDuration + const Duration(milliseconds: 100);

  late Character player;
  late Character enemy;

  late StageModel currentStage;
  EnemyIntent? currentEnemyIntent;

  final BattleLogManager _logManager = BattleLogManager();
  bool isPlayerTurn = true;
  int _turnTransactionId = 0;
  int stage = 1;
  int turnCount = 1;
  List<Item> rewardOptions = [];
  bool showRewardPopup = false;
  int _lastGoldReward = 0;

  final ShopProvider shopProvider;
  final Random _random;

  BattleProvider({required this.shopProvider, Random? random})
    : _random = random ?? Random();

  List<String> get logs => _logManager.logs;
  int get lastGoldReward => _lastGoldReward;
  StageType get nextStageType => StageManager.getStageTypeFor(stage + 1);

  StageType getStageTypeFor(int stageNumber) {
    return StageManager.getStageTypeFor(stageNumber);
  }

  void refreshUI() {
    notifyListeners();
  }

  // Streams for UI Feedback
  final _damageEventController = StreamController<DamageEvent>.broadcast();
  Stream<DamageEvent> get damageStream => _damageEventController.stream;

  final _effectEventController = StreamController<EffectEvent>.broadcast();
  Stream<EffectEvent> get effectStream => _effectEventController.stream;

  final _healEventController = StreamController<HealEvent>.broadcast();
  Stream<HealEvent> get healStream => _healEventController.stream;

  @override
  void dispose() {
    _damageEventController.close();
    _effectEventController.close();
    _healEventController.close();
    super.dispose();
  }

  // --- Initialization & Flow ---

  void initializeBattle() {
    player =
        PlayerTable.get("warrior")?.createCharacter() ??
        Character(name: "Player", maxHp: 50, armor: 0, atk: 5, baseDefense: 5);
    player.gold = GameConfig.startingGold;
    stage = 1;

    // Starting items
    var healPotion = ItemTable.get('potion_heal_small');
    var armorPotion = ItemTable.get('potion_armor_small');
    var strPotion = ItemTable.get('potion_strength_small');

    _addStartingItem('war_hammer'); // Stun test weapon
    _addStartingItem('barbed_net'); // Bleed test weapon
    _addStartingItem('hooked_spear'); // Disarm test weapon
    _addStartingItem('cursed_shield'); // Defense-forbidden test shield
    if (healPotion != null) player.addToInventory(healPotion.createItem());
    if (armorPotion != null) player.addToInventory(armorPotion.createItem());
    if (strPotion != null) player.addToInventory(strPotion.createItem());

    _prepareNextStage();
    _logManager.clear();
    _addLog("Game Started! Stage 1");
    notifyListeners();
  }

  void _addStartingItem(String itemId) {
    final item = ItemTable.get(itemId);
    if (item != null) {
      player.addToInventory(item.createItem());
    }
  }

  void loadFromSave(Map<String, dynamic> data) {
    player = Character.fromJson(data['player']);
    stage = data['stage'] ?? 1;
    _prepareNextStage();
    _logManager.clear();
    _addLog("Game Loaded! Stage $stage");
    notifyListeners();
  }

  void _prepareNextStage() {
    _turnTransactionId++;
    SaveManager.saveGame(this);

    player.armor = 0;
    StageType type = StageManager.getStageTypeFor(stage);

    enemy = StageManager.generateEnemy(stage, type);
    List<Item> shopItems = [];

    if (type == StageType.battle || type == StageType.elite) {
      isPlayerTurn = true;
      showRewardPopup = false;
      _generateEnemyIntent();
    } else if (type == StageType.shop) {
      shopProvider.generateShopItems(stage);
      shopItems = shopProvider.availableItems;
      _addLog("Stage $stage: Entered a Shop.");
    } else if (type == StageType.rest) {
      _addLog("Stage $stage: Found a safe resting spot.");
    }

    currentStage = StageModel(type: type, enemy: enemy, shopItems: shopItems);
    turnCount = 1;
    notifyListeners();
  }

  // --- Combat Logic ---

  Future<void> playerAction(ActionType type, RiskLevel risk) async {
    if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup) {
      return;
    }

    applyPendingEnemyDefense();

    if (type == ActionType.defend &&
        player.hasStatus(StatusEffectType.defenseForbidden)) {
      _addLog("Cannot defend! You are under Defense Forbidden status.");
      notifyListeners();
      return;
    }

    isPlayerTurn = false;
    notifyListeners();

    TurnEffectResult turnEffect = _processStartTurnEffects(player);

    if (player.isDead) {
      await _onDefeat();
      return;
    }

    if (turnEffect.effectTriggered) {
      await Future.delayed(_turnEffectVisualDelay);
    }

    if (!turnEffect.canAct) {
      _endPlayerTurn();
      return;
    }

    _addLog("Player chose to ${type.name} with ${risk.name} risk.");

    int baseValue = (type == ActionType.attack)
        ? player.totalAtk
        : player.totalDefense;
    final result = CombatCalculator.calculateActionOutcome(
      actionType: type,
      risk: risk,
      luck: player.totalLuck,
      baseValue: baseValue,
      random: _random,
    );

    if (result.success) {
      if (type == ActionType.attack) {
        if (CombatCalculator.calculateDodge(
          enemy.totalDodge,
          random: _random,
        )) {
          _addLog("${enemy.name} dodged the attack!");
          _effectEventController.sink.add(
            EffectEventFactory.createDodgeEvent(
              attacker: player,
              target: enemy,
              effectTarget: EffectTarget.enemy,
              risk: risk,
              random: _random,
            ),
          );
        } else {
          _effectEventController.sink.add(
            EffectEventFactory.createAttackEvent(
              attacker: player,
              target: enemy,
              effectTarget: EffectTarget.enemy,
              risk: risk,
              damage: result.value,
              random: _random,
            ),
          );
        }
      } else {
        _effectEventController.sink.add(
          EffectEventFactory.createDefenseEvent(
            attacker: player,
            target: player,
            effectTarget: EffectTarget.player,
            risk: risk,
            armorGained: result.value,
            random: _random,
          ),
        );
      }
    } else {
      final event = EffectEventFactory.createFailureEvent(
        attacker: player,
        target: (type == ActionType.attack) ? enemy : player,
        effectTarget: (type == ActionType.attack)
            ? EffectTarget.enemy
            : EffectTarget.player,
        type: type,
        risk: risk,
        random: _random,
      );
      _effectEventController.sink.add(event);
      _addLog("${player.name}'s ${type.name} ${event.feedbackType!.name}!");
    }

    if (enemy.isDead) {
      _onVictory();
    }
  }

  void _endPlayerTurn() {
    if (enemy.isDead) return;
    isPlayerTurn = false;
    player.updateEndOfTurnStatusEffects();

    final tid = _turnTransactionId;
    Future.delayed(
      const Duration(milliseconds: GameConfig.animDelayEnemyTurn),
      () {
        if (tid != _turnTransactionId) return;
        _processEnemyTurn();
      },
    );
  }

  Future<void> _processEnemyTurn() async {
    if (enemy.isDead || player.isDead) return;

    turnCount++;
    TurnEffectResult turnEffect = _processStartTurnEffects(enemy);

    if (enemy.isDead) {
      _onVictory();
      return;
    }

    if (turnEffect.effectTriggered) {
      await Future.delayed(_turnEffectVisualDelay);
    }

    if (currentEnemyIntent != null && turnEffect.canAct) {
      final intent = currentEnemyIntent!;

      if (intent.type == EnemyActionType.defend) {
        _addLog("${enemy.name} maintains defensive stance.");
        _effectEventController.sink.add(
          EffectEventFactory.createDefenseEvent(
            attacker: enemy,
            target: enemy,
            effectTarget: EffectTarget.enemy,
            risk: intent.risk,
            armorGained: intent.finalValue,
            random: _random,
          ),
        );
      } else {
        if (intent.isSuccess) {
          if (CombatCalculator.calculateDodge(
            player.totalDodge,
            random: _random,
          )) {
            _addLog("${player.name} dodged the attack!");
            _effectEventController.sink.add(
              EffectEventFactory.createDodgeEvent(
                attacker: enemy,
                target: player,
                effectTarget: EffectTarget.player,
                risk: intent.risk,
                random: _random,
              ),
            );
          } else {
            int finalDamage =
                (enemy.totalAtk *
                        CombatCalculator.getEfficiency(
                          ActionType.attack,
                          intent.risk,
                        ))
                    .toInt();
            if (finalDamage < 1 && enemy.totalAtk > 0) finalDamage = 1;

            _effectEventController.sink.add(
              EffectEventFactory.createAttackEvent(
                attacker: enemy,
                target: player,
                effectTarget: EffectTarget.player,
                risk: intent.risk,
                damage: finalDamage,
                random: _random,
              ),
            );
          }
        } else {
          _addLog("Enemy's ${intent.risk.name} attack missed!");
          _effectEventController.sink.add(
            EffectEventFactory.createFailureEvent(
              attacker: enemy,
              target: player,
              effectTarget: EffectTarget.player,
              type: ActionType.attack,
              risk: intent.risk,
              random: _random,
            ),
          );
        }
      }
    } else if (!turnEffect.canAct) {
      _addLog("Enemy is stunned and cannot act!");
      int tid = _turnTransactionId;
      Future.delayed(const Duration(milliseconds: 500), () {
        if (tid != _turnTransactionId) return;
        _endEnemyTurn();
      });
    } else {
      _addLog("Enemy did nothing.");
      int tid = _turnTransactionId;
      Future.delayed(const Duration(milliseconds: 500), () {
        if (tid != _turnTransactionId) return;
        _endEnemyTurn();
      });
    }
  }

  void _endEnemyTurn() {
    if (player.isDead) return;
    enemy.updateEndOfTurnStatusEffects();
    _generateEnemyIntent();
    _startPlayerTurn();
  }

  void _startPlayerTurn() {
    isPlayerTurn = true;
    notifyListeners();
  }

  TurnEffectResult _processStartTurnEffects(Character character) {
    Map<String, dynamic> result = CombatCalculator.processStartTurnEffects(
      character,
    );
    int bleedDamage = result['bleedDamage'];
    bool isStunned = result['isStunned'];

    if (bleedDamage > 0) {
      character.hp -= bleedDamage;
      if (character.hp < 0) character.hp = 0;
      _addLog("${character.name} took $bleedDamage bleed damage.");

      _damageEventController.sink.add(
        DamageEvent(
          damage: bleedDamage,
          armorDamage: 0,
          target: (character == player)
              ? DamageTarget.player
              : DamageTarget.enemy,
          type: DamageType.bleed,
        ),
      );
    }

    character.updateStartOfTurnStatusEffects();
    return TurnEffectResult(
      canAct: !isStunned,
      effectTriggered: bleedDamage > 0 || isStunned,
    );
  }

  // --- Post-Animation Impacts ---

  void handleImpact(EffectEvent event) {
    if (event.isVisualOnly) {
      notifyListeners();
      return;
    }

    if ((event.isSuccess == false || event.feedbackType != null) &&
        event.type != ActionType.defend) {
      notifyListeners();
      if (event.attacker == player) {
        _endPlayerTurn();
      } else if (event.attacker == enemy) {
        _endEnemyTurn();
      }
      return;
    }

    _processAttackImpact(event);

    if (event.triggersTurnChange) {
      if (event.attacker == player) {
        _endPlayerTurn();
      } else if (event.attacker == enemy) {
        _endEnemyTurn();
      }
    }
  }

  void _processAttackImpact(EffectEvent event) {
    final attacker = event.attacker!;
    final target = event.targetEntity!;

    if (event.type == ActionType.attack) {
      int incomingDamage = event.damageValue!;

      int damageToHp = CombatCalculator.calculateDamageToHp(
        incomingDamage: incomingDamage,
        currentArmor: target.armor,
        isVulnerable: target.hasStatus(StatusEffectType.vulnerable),
      );

      int remainingArmor = CombatCalculator.calculateRemainingArmor(
        incomingDamage: incomingDamage,
        currentArmor: target.armor,
        isVulnerable: target.hasStatus(StatusEffectType.vulnerable),
      );

      int armorDamage = target.armor - remainingArmor;
      target.armor = remainingArmor;

      if (damageToHp > 0) {
        target.hp -= damageToHp;
        if (target.hp < 0) target.hp = 0;
      }

      if (damageToHp > 0 || armorDamage > 0) {
        _damageEventController.sink.add(
          DamageEvent(
            damage: damageToHp,
            armorDamage: armorDamage,
            target: (target == player)
                ? DamageTarget.player
                : DamageTarget.enemy,
            type: target.hasStatus(StatusEffectType.vulnerable)
                ? DamageType.vulnerable
                : DamageType.normal,
            risk: event.risk,
          ),
        );
      }

      if (damageToHp > 0) {
        _addLog("${attacker.name} dealt $damageToHp damage to ${target.name}.");
      } else if (armorDamage > 0) {
        _addLog("${attacker.name}'s attack was fully blocked by armor.");
      }

      _tryApplyStatusEffects(attacker, target, damageToHp);

      if (target == enemy) {
        updateEnemyIntent();
      }
    } else if (event.type == ActionType.defend) {
      if (event.isSuccess!) {
        int armorGained = event.armorGained!;
        target.armor += armorGained;
        _addLog("${target.name} gained $armorGained armor.");
      } else {
        _addLog("${target.name}'s defense failed!");
      }
    }

    if (target.isDead) {
      if (target == player) {
        _onDefeat();
      } else {
        _onVictory();
      }
    }
    notifyListeners();
  }

  // --- Rewards & Victory ---

  void _onVictory() {
    if (stage >= GameConfig.maxStage) {
      _onFinalVictory();
      return;
    }

    int goldReward = StageManager.calculateGoldReward(stage, _random);
    player.gold += goldReward;
    _lastGoldReward = goldReward;
    _addLog("Enemy defeated! Gained $goldReward Gold.");
    _addLog("Choose a reward.");

    rewardOptions = StageManager.generateRewardOptions(
      stage,
      currentStage.type,
    );
    showRewardPopup = true;
    notifyListeners();
  }

  void _onFinalVictory() {
    _addLog("CONGRATULATIONS! You have conquered the dungeon!");
    // You could set a flag like isGameCompleted = true here
    notifyListeners();
  }

  Future<void> _onDefeat() async {
    _addLog("Player defeated! Enemy wins!");
    await SaveManager.clearSaveData();
    notifyListeners();
  }

  bool selectReward(Item item, {bool completeStage = true}) {
    if (item.id == "reward_skip") {
      _addLog("Skipped reward.");
    } else {
      if (player.addToInventory(item)) {
        _addLog("Gained ${item.name}.");
      } else {
        _addLog("Inventory full! Could not take ${item.name}.");
        return false;
      }
    }

    if (completeStage) {
      showRewardPopup = false;
      stage++;
      _prepareNextStage();
    }
    notifyListeners();
    return true;
  }

  // --- Helper Methods ---

  void _addLog(String message) {
    _logManager.addLog(message);
    notifyListeners();
  }

  void _generateEnemyIntent() {
    currentEnemyIntent = EnemyAIService.generateIntent(enemy, _random);
    notifyListeners();
  }

  void generateEnemyIntent() {
    _generateEnemyIntent();
  }

  /// Recalculates the currently telegraphed enemy intent without changing
  /// its action type, risk, or success roll.
  void updateEnemyIntent() {
    if (currentEnemyIntent == null || enemy.isDead) return;

    final intent = currentEnemyIntent!;
    final actionType = intent.type == EnemyActionType.attack
        ? ActionType.attack
        : ActionType.defend;
    final baseValue = intent.type == EnemyActionType.attack
        ? enemy.totalAtk
        : enemy.totalDefense;
    var newValue =
        (baseValue * CombatCalculator.getEfficiency(actionType, intent.risk))
            .toInt();

    if (newValue < 1 && baseValue > 0) {
      newValue = 1;
    }

    currentEnemyIntent = EnemyIntent(
      type: intent.type,
      value: newValue,
      risk: intent.risk,
      description: "$newValue (${intent.risk.name})",
      isSuccess: intent.isSuccess,
      finalValue: newValue,
      isApplied: intent.isApplied,
    );
    notifyListeners();
  }

  void applyPendingEnemyDefense() {
    if (currentEnemyIntent != null &&
        currentEnemyIntent!.type == EnemyActionType.defend &&
        !currentEnemyIntent!.isApplied) {
      final intent = currentEnemyIntent!;
      if (intent.isSuccess) {
        enemy.armor += intent.finalValue;
        _addLog(
          "${enemy.name} assumes a defensive stance (+${intent.finalValue} Armor).",
        );
      } else {
        _addLog("${enemy.name} tried to defend but failed.");
      }
      intent.isApplied = true;
      notifyListeners();
    }
  }

  void _tryApplyStatusEffects(
    Character attacker,
    Character target,
    int damageToHp,
  ) {
    List<StatusEffect> effectsToApply = CombatCalculator.getAppliedEffects(
      attacker,
      random: _random,
    );
    for (var effect in effectsToApply) {
      if (effect.type == StatusEffectType.bleed && damageToHp <= 0) continue;
      target.addStatusEffect(effect);
      _addLog("Applied ${effect.type.name} to ${target.name}!");
    }
  }

  // --- Equipment Management ---

  void equipItem(Item item, {EquipmentSlot? targetSlot}) {
    bool success = false;
    if (targetSlot != null) {
      success = player.equipToSlot(item, targetSlot);
    } else {
      success = player.equip(item);
    }

    if (success) {
      _addLog("Equipped ${item.name}.");
      notifyListeners();
    }
  }

  void unequipItem(Item item) {
    if (player.unequip(item)) {
      _addLog("Unequipped ${item.name}.");
      notifyListeners();
    }
  }

  void sellItem(Item item) {
    int price = (item.price * GameConfig.sellPriceMultiplier).floor();
    player.gold += price;
    player.inventory.remove(item);
    _addLog("Sold ${item.name} for $price G.");
    notifyListeners();
  }

  void discardItem(Item item) {
    player.inventory.remove(item);
    _addLog("Discarded ${item.name}.");
    notifyListeners();
  }

  void useConsumable(Item item) {
    if (item.slot != EquipmentSlot.consumable) {
      _addLog("Cannot use ${item.name}.");
      return;
    }

    var effectApplied = false;

    if (item.hpBonus > 0) {
      final hpBefore = player.hp;
      player.heal(item.hpBonus);
      final healedAmount = player.hp - hpBefore;

      if (healedAmount > 0) {
        _addLog("Used ${item.name}: Healed $healedAmount HP.");
        _healEventController.sink.add(
          HealEvent(amount: healedAmount, target: HealTarget.player),
        );
      } else {
        _addLog("Used ${item.name}: HP is already full.");
      }
      effectApplied = true;
    }

    if (item.armorBonus > 0) {
      player.armor += item.armorBonus;
      _addLog("Used ${item.name}: Gained ${item.armorBonus} Armor.");
      effectApplied = true;
    }

    for (var effect in item.effects) {
      if (effect.type == StatusEffectType.heal) {
        final hpBefore = player.hp;
        player.heal(effect.value);
        final healedAmount = player.hp - hpBefore;

        if (healedAmount > 0) {
          _addLog("Used ${item.name}: Healed $healedAmount HP.");
          _healEventController.sink.add(
            HealEvent(amount: healedAmount, target: HealTarget.player),
          );
        } else {
          _addLog("Used ${item.name}: HP is already full.");
        }
      } else {
        player.addStatusEffect(
          StatusEffect(
            type: effect.type,
            duration: effect.duration,
            value: effect.value,
          ),
        );
        _addLog("Used ${item.name}: Applied ${effect.type.name}.");
      }
      effectApplied = true;
    }

    if (effectApplied) {
      player.inventory.remove(item);
      notifyListeners();
    }
  }

  void proceedToNextStage() {
    stage++;
    _prepareNextStage();
  }
}
