import 'dart:async'; // StreamController 사용을 위해 import
import 'dart:math';

import 'package:flutter/material.dart';
import '../game/model/entity.dart';
import '../game/model/item.dart';
import '../game/model/status_effect.dart';
import '../game/model/stage.dart';
import '../game/data/item_table.dart';

import '../game/data/enemy_table.dart';
import '../game/data/player_table.dart';
import '../utils/game_math.dart';
import '../game/enums.dart';
import '../game/model/damage_event.dart'; // DamageEvent import
import '../game/model/effect_event.dart'; // EffectEvent import

class EnemyIntent {
  final EnemyActionType type;
  final int value;
  final RiskLevel risk;
  final String description;
  final bool isSuccess;
  final int finalValue;

  EnemyIntent({
    required this.type,
    required this.value,
    required this.risk,
    required this.description,
    required this.isSuccess,
    required this.finalValue,
  });
}

class BattleProvider with ChangeNotifier {
  late Character player;
  late Character enemy; // Kept for compatibility, active during Battle/Elite

  late StageModel currentStage; // The current stage object
  EnemyIntent? currentEnemyIntent;

  List<String> battleLogs = [];
  bool isPlayerTurn = true;

  int stage = 1;
  int turnCount = 1;
  List<Item> rewardOptions = [];
  bool showRewardPopup = false;

  List<String> get logs => battleLogs;

  // Damage Event Stream
  final _damageEventController = StreamController<DamageEvent>.broadcast();
  Stream<DamageEvent> get damageStream => _damageEventController.stream;

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

  BattleProvider() {
    // initializeBattle(); // Do not auto-start logic
  }

  @override
  void dispose() {
    _damageEventController.close(); // StreamController 닫기
    _effectEventController.close();
    super.dispose();
  }

  void initializeBattle() {
    stage = 1;
    turnCount = 1;
    // Load player from PlayerTable
    final playerTemplate = PlayerTable.get("warrior");
    if (playerTemplate != null) {
      player = playerTemplate.createCharacter();
    } else {
      // Fallback if data is missing
      player = Character(
        name: "Player",
        maxHp: 50,
        armor: 0,
        atk: 5,
        baseDefense: 5,
      );
    }

    // Provide starter equipment
    final starterSword = Item(
      id: "starter_sword",
      name: "Wooden Sword",
      description: "A basic sword",
      atkBonus: 5,
      hpBonus: 0,
      slot: EquipmentSlot.weapon,
    );
    final starterArmor = Item(
      id: "starter_armor",
      name: "Leather Armor",
      description: "Basic protection",
      atkBonus: 0,
      hpBonus: 20,
      slot: EquipmentSlot.armor,
    );
    final starterShield = Item(
      id: "starter_shield",
      name: "Wooden Shield",
      description: "A small shield",
      atkBonus: 0,
      hpBonus: 0,
      armorBonus: 3,
      slot: EquipmentSlot.shield,
    );
    final starterRing = Item(
      id: "starter_ring",
      name: "Copper Ring",
      description: "A simple ring",
      atkBonus: 1,
      hpBonus: 5,
      slot: EquipmentSlot.accessory,
    );

    player.addToInventory(starterSword);
    player.equip(starterSword);

    player.addToInventory(starterArmor);
    player.equip(starterArmor);

    player.addToInventory(starterShield);
    player.equip(starterShield);

    player.addToInventory(starterRing);
    player.equip(starterRing);

    // Add new status effect items for testing
    player.addToInventory(ItemTable.weapons[3].createItem()); // Stunning Hammer
    player.addToInventory(ItemTable.weapons[4].createItem()); // Jagged Dagger
    player.addToInventory(ItemTable.weapons[5].createItem()); // Sunderer Axe
    player.addToInventory(ItemTable.shields[3].createItem()); // Cursed Shield

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

  void _prepareNextStage() {
    StageType type;

    // Stage Type Logic
    if (stage % 10 == 0) {
      type = StageType.elite; // Every 10th stage is a Boss/Elite
    } else if (stage % 5 == 0) {
      type = StageType.shop; // Every 5th stage is a Shop (except 10, 20...)
    } else if (stage % 8 == 0) {
      type = StageType.rest; // Every 8th stage is a Rest
    } else {
      type = StageType.battle;
    }

    // Prepare Data based on Type
    Character? newEnemy;
    List<Item> shopItems = [];

    if (type == StageType.battle || type == StageType.elite) {
      bool isElite = type == StageType.elite;
      // Select random enemy template
      final random = Random();
      EnemyTemplate template;
      if (isElite) {
        if (EnemyTable.eliteEnemies.isNotEmpty) {
          template = EnemyTable
              .eliteEnemies[random.nextInt(EnemyTable.eliteEnemies.length)];
        } else {
          // Fallback if no elite enemies loaded
          template = const EnemyTemplate(
            name: "Elite Guardian",
            baseHp: 50,
            baseAtk: 10,
            baseDefense: 2,
          );
        }
      } else {
        if (EnemyTable.normalEnemies.isNotEmpty) {
          template = EnemyTable
              .normalEnemies[random.nextInt(EnemyTable.normalEnemies.length)];
        } else {
          // Fallback
          template = const EnemyTemplate(
            name: "Enemy",
            baseHp: 20,
            baseAtk: 5,
            baseDefense: 0,
          );
        }
      }

      newEnemy = template.createCharacter(stage: stage);

      // Assign to the main 'enemy' field for UI compatibility
      enemy = newEnemy;
      isPlayerTurn = true;
      showRewardPopup = false;

      _generateEnemyIntent(); // Generate first intent

      _addLog("Stage $stage ($type) started! A wild ${enemy.name} appeared.");
    } else if (type == StageType.shop) {
      // Generate random items for shop
      final random = Random();
      List<ItemTemplate> allTemplates = List.from(ItemTable.allItems);
      allTemplates.shuffle(random);

      int count = min(4, allTemplates.length);
      shopItems = allTemplates
          .sublist(0, count)
          .map((t) => t.createItem(stage: stage))
          .toList();

      // Dummy enemy to prevent null errors in existing UI (until UI is fully updated)
      enemy = Character(name: "Merchant", maxHp: 9999, armor: 0, atk: 0);

      _addLog("Stage $stage: Entered a Shop.");
    } else if (type == StageType.rest) {
      // Dummy enemy
      enemy = Character(name: "Campfire", maxHp: 9999, armor: 0, atk: 0);
      _addLog("Stage $stage: Found a safe resting spot.");
    }

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

  // Replaces _spawnEnemy
  // void _spawnEnemy() { ... } - Removed

  /// Handle player's action choice

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

    // Update Enemy Status Effects at the start of Player's turn (user request)
    enemy.updateStatusEffects();

    // 1. Check for Defense Forbidden status
    if (type == ActionType.defend &&
        player.hasStatus(StatusEffectType.defenseForbidden)) {
      _addLog("Cannot defend! You are under Defense Forbidden status.");
      notifyListeners(); // 상태 변경을 알림
      _endPlayerTurn();
      return;
    }

    isPlayerTurn = false;
    notifyListeners();

    // 2. Process Start-of-Turn Effects (Stun, Bleed)
    bool canAct = _processStartTurnEffects(player);
    if (!canAct) {
      _endPlayerTurn(); // Skip turn if stunned
      return;
    }

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

    final random = Random();
    bool success = false;
    double efficiency = 1.0;

    switch (risk) {
      case RiskLevel.safe:
        // Safe: 100% base chance + luck
        double chance = 1.0 + (player.totalLuck / 100.0);
        if (chance > 1.0) chance = 1.0;
        success = random.nextDouble() < chance;
        efficiency = 0.5; // 50%
        break;
      case RiskLevel.normal:
        // Normal: 80% base chance + luck
        double chance = 0.8 + (player.totalLuck / 100.0);
        if (chance > 1.0) chance = 1.0;
        success = random.nextDouble() < chance;
        efficiency = 1.0; // 100%
        break;
      case RiskLevel.risky:
        // Risky: 40% base chance + luck
        double chance = 0.4 + (player.totalLuck / 100.0);
        if (chance > 1.0) chance = 1.0;
        success = random.nextDouble() < chance;
        efficiency = 2.0; // 200%
        break;
    }

    if (success) {
      if (type == ActionType.attack) {
        int damage = (player.totalAtk * efficiency).toInt();

        _effectEventController.sink.add(
          EffectEvent(
            type: ActionType.attack,
            risk: risk,
            target: EffectTarget.enemy,
            feedbackType: null, // 공격 성공이므로 feedbackType 없음
          ),
        );

        int damageToHp = 0;
        if (enemy.armor > 0) {
          if (enemy.armor >= damage) {
            enemy.armor -= damage;
            damageToHp = 0;
            _addLog("Enemy's armor absorbed all $damage damage.");
          } else {
            damageToHp = damage - enemy.armor;
            _addLog("Enemy's armor absorbed ${enemy.armor} damage.");
            enemy.armor = 0;
          }
        } else {
          damageToHp = damage;
        }

        if (damageToHp > 0) {
          _applyDamage(enemy, damageToHp, targetType: DamageTarget.enemy);
          _addLog("Player dealt $damageToHp damage to Enemy.");
        } else {
          _addLog("Player's attack was fully blocked by armor.");
        }

        // Try applying status effects from items
        _tryApplyStatusEffects(player, enemy);
      } else {
        _effectEventController.sink.add(
          EffectEvent(
            type: ActionType.defend,
            risk: risk,
            target: EffectTarget.player,
            feedbackType: null, // 방어 성공이므로 feedbackType 없음
          ),
        );

        int armorGained = (player.totalDefense * efficiency).toInt();
        player.armor += armorGained;
        _addLog("Player gained $armorGained armor.");
      }
    } else {
      if (type == ActionType.attack) {
        _addLog("Player's attack missed!");
        _effectEventController.sink.add(
          EffectEvent(
            type: type,
            risk: risk,
            target: EffectTarget.enemy, // 공격 실패는 적 위치에 MISS
            feedbackType: BattleFeedbackType.miss,
          ),
        );
      } else {
        _addLog("Player's defense failed!");
        _effectEventController.sink.add(
          EffectEvent(
            type: type,
            risk: risk,
            target: EffectTarget.player, // 방어 실패는 내 위치에 FAILED
            feedbackType: BattleFeedbackType.failed,
          ),
        );
      }
    }

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

    _endPlayerTurn();
  }

  void _endPlayerTurn() {
    // Update durations at end of turn
    player.updateStatusEffects();

    // Check if enemy is dead from bleed
    if (enemy.isDead) {
      _onVictory();
      return;
    }

    Future.delayed(const Duration(seconds: 1), () => _enemyTurn());
  }

  Future<void> _enemyTurn() async {
    if (!isPlayerTurn && (player.isDead || enemy.isDead)) return;

    _addLog("Enemy's turn...");
    await Future.delayed(const Duration(seconds: 1));

    // Enemy Turn Start Logic
    // Armor decay
    if (enemy.armor > 0) {
      enemy.armor = (enemy.armor * 0.5).toInt();
      _addLog("Enemy's armor decayed to ${enemy.armor}.");
    }

    // 1. Process Start-of-Turn Effects for Enemy
    bool canAct = _processStartTurnEffects(enemy);

    // Check death from bleed before acting
    if (enemy.isDead) {
      _onVictory();
      return;
      // return; // Already handled by _processStartTurnEffects if damage applied
    }

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

      if (intent.type == EnemyActionType.defend) {
        // Already handled in _generateEnemyIntent
        _addLog("Enemy maintains defensive stance.");
      } else {
        // Attack Logic
        if (intent.isSuccess) {
          _effectEventController.sink.add(
            EffectEvent(
              type: ActionType.attack,
              risk: intent.risk,
              target: EffectTarget.player,
              feedbackType: null, // 공격 성공이므로 feedbackType 없음
            ),
          );

          int incomingDamage = intent.finalValue;
          int damageToHp = 0;

          // Handle Player Armor
          if (player.armor > 0) {
            if (player.armor >= incomingDamage) {
              player.armor -= incomingDamage;
              damageToHp = 0;
              _addLog("Armor absorbed all $incomingDamage damage.");
            } else {
              damageToHp = incomingDamage - player.armor;
              _addLog("Armor absorbed ${player.armor} damage.");
              player.armor = 0;
            }
          } else {
            damageToHp = incomingDamage;
          }

          if (damageToHp > 0) {
            _applyDamage(player, damageToHp, targetType: DamageTarget.player);
            _addLog("Enemy dealt $damageToHp damage to Player HP.");
          }

          // Try applying status effects from enemy equipment
          _tryApplyStatusEffects(enemy, player);
        } else {
          _addLog("Enemy's ${intent.risk.name} attack missed!");
          _effectEventController.sink.add(
            EffectEvent(
              type: ActionType.attack, // 적의 공격이므로 ActionType.attack
              risk: intent.risk,
              target: EffectTarget.player, // 플레이어가 회피했으므로 플레이어 위치에 이펙트
              feedbackType: BattleFeedbackType.miss, // 변경: MISS 피드백
            ),
          );
        }
      }
    } else if (!canAct) {
      _addLog("Enemy is stunned and cannot act!");
    } else {
      _addLog("Enemy did nothing.");
    }

    // Generate next intent
    if (!player.isDead) {
      _generateEnemyIntent();
    }

    // Player Turn Start Logic
    // Armor decay
    if (player.armor > 0) {
      player.armor = (player.armor * 0.5).toInt();
      _addLog("Player's armor decayed to ${player.armor}.");
    }

    if (player.isDead) {
      _addLog("Player defeated! Enemy wins!");
    }

    isPlayerTurn = true;
    turnCount++;
    notifyListeners();
  }

  /// Process effects that happen at the start of the turn (Bleed, Stun).
  /// Returns true if the character can act, false if stunned.
  bool _processStartTurnEffects(Character character) {
    bool canAct = true;

    // 1. Bleed Damage
    var bleedEffects = character.statusEffects
        .where((e) => e.type == StatusEffectType.bleed)
        .toList();
    if (bleedEffects.isNotEmpty) {
      int totalBleed = bleedEffects.fold(0, (sum, e) => sum + e.value);
      int previousHp = character.hp; // Record HP before damage
      character.hp -= totalBleed;
      if (character.hp < 0) character.hp = 0;
      _addLog("${character.name} takes $totalBleed bleed damage!");

      // Emit DamageEvent for bleed
      if (character == player) {
        _damageEventController.sink.add(
          DamageEvent(
            damage: totalBleed,
            target: DamageTarget.player,
            type: DamageType.bleed,
          ),
        );
      } else if (character == enemy) {
        _damageEventController.sink.add(
          DamageEvent(
            damage: totalBleed,
            target: DamageTarget.enemy,
            type: DamageType.bleed,
          ),
        );
      }
    }

    // 2. Stun Check
    if (character.hasStatus(StatusEffectType.stun)) {
      canAct = false;
      _addLog("${character.name} is stunned!");
    }

    return canAct;
  }

  /// Tries to apply status effects from attacker's equipment to the target.
  void _tryApplyStatusEffects(Character attacker, Character target) {
    final random = Random();

    for (var item in attacker.equipment.values) {
      for (var effect in item.effects) {
        // Roll for probability (0-100)
        if (random.nextInt(100) < effect.probability) {
          // Apply effect
          final newStatus = StatusEffect(
            type: effect.type,
            duration: effect.duration,
            value: effect.value,
          );
          target.addStatusEffect(newStatus);
          _addLog("Applied ${effect.type.name} to ${target.name}!");
        }
      }
    }
  }

  void _applyDamage(
    Character target,
    int damage, {
    required DamageTarget targetType,
    DamageType type = DamageType.normal,
  }) {
    // Check Vulnerable
    if (target.hasStatus(StatusEffectType.vulnerable)) {
      damage = (damage * 1.5).toInt();
      _addLog("Vulnerable! Damage increased to $damage.");
      type = DamageType.vulnerable;
    }

    target.hp -= damage;
    if (target.hp < 0) target.hp = 0;

    _damageEventController.sink.add(
      DamageEvent(damage: damage, target: targetType, type: type),
    );
  }

  void _addLog(String message) {
    battleLogs.add(message);
    notifyListeners();
  }

  void _onVictory() {
    _addLog("Enemy defeated! Choose a reward.");

    final random = Random();
    List<ItemTemplate> allTemplates = List.from(ItemTable.allItems);
    allTemplates.shuffle(random); // Shuffle to randomize selection

    // Take first 3 items (ensure distinct templates if possible, though list is small now)
    int count = min(3, allTemplates.length);
    rewardOptions = allTemplates.sublist(0, count).map((template) {
      return template.createItem(stage: stage);
    }).toList();

    showRewardPopup = true;
    notifyListeners();
  }

  void selectReward(Item item) {
    bool added = player.addToInventory(item);
    if (added) {
      _addLog("Added ${item.name} to inventory.");
    } else {
      _addLog("Inventory is full! ${item.name} discarded.");
    }

    // Heal player after selecting reward
    int healAmount = GameMath.floor(player.totalMaxHp * 0.1);
    player.heal(healAmount);
    _addLog("Stage Cleared! Recovered $healAmount HP.");

    stage++;
    showRewardPopup = false;

    _prepareNextStage();

    // Log moved to _prepareNextStage

    // isPlayerTurn = true; // Handled in _prepareNextStage for battles
    notifyListeners();
  }

  void equipItem(Item item) {
    if (player.equip(item)) {
      _addLog("Equipped ${item.name}.");
    } else {
      _addLog(
        "Failed to equip ${item.name}.",
      ); // Should not happen if logic is correct
    }
    notifyListeners();
  }

  void unequipItem(Item item) {
    if (player.unequip(item)) {
      _addLog("Unequipped ${item.name}.");
    } else {
      _addLog("Failed to unequip ${item.name} (Inventory might be full).");
    }
    notifyListeners();
  }

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

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

  /// Proceed to next stage from non-battle stages (Shop, Rest)
  void proceedToNextStage() {
    stage++;
    _prepareNextStage();
  }

  void _generateEnemyIntent() {
    if (enemy.isDead) {
      currentEnemyIntent = null;
      return;
    }

    final random = Random();

    // Decide Action Type
    // If baseDefense is 0, CANNOT defend.
    bool canDefend = enemy.baseDefense > 0;
    // Check for DefenseForbidden status
    if (enemy.hasStatus(StatusEffectType.defenseForbidden)) {
      canDefend = false;
    }
    bool isAttack = true;

    if (canDefend) {
      // 70% Attack, 30% Defend
      isAttack = random.nextDouble() < 0.7;
    } else {
      isAttack = true;
    }

    // Decide Risk Level
    RiskLevel risk = RiskLevel.values[random.nextInt(RiskLevel.values.length)];
    double efficiency = 1.0;
    switch (risk) {
      case RiskLevel.safe:
        efficiency = 0.5;
        break;
      case RiskLevel.normal:
        efficiency = 1.0;
        break;
      case RiskLevel.risky:
        efficiency = 2.0;
        break;
    }

    if (isAttack) {
      // Attack Intent
      // Variance removed as per request
      int damage = (enemy.totalAtk * efficiency).toInt();
      if (damage < 1) damage = 1;

      // Calculate success immediately
      bool success = false;
      switch (risk) {
        case RiskLevel.safe:
          success = random.nextDouble() < 1.0;
          break;
        case RiskLevel.normal:
          success = random.nextDouble() < 0.8;
          break;
        case RiskLevel.risky:
          success = random.nextDouble() < 0.4;
          break;
      }

      currentEnemyIntent = EnemyIntent(
        type: EnemyActionType.attack,
        value: damage,
        risk: risk,
        description: "Attacks for $damage (${risk.name})",
        isSuccess: success,
        finalValue: damage,
      );
    } else {
      // Defend Intent
      int baseDef = enemy.totalDefense;
      // Variance removed
      int armor = (baseDef * 2 * efficiency).toInt();

      // Calculate success immediately
      bool success = false;
      switch (risk) {
        case RiskLevel.safe:
          success = random.nextDouble() < 1.0;
          break;
        case RiskLevel.normal:
          success = random.nextDouble() < 0.8;
          break;
        case RiskLevel.risky:
          success = random.nextDouble() < 0.4;
          break;
      }

      currentEnemyIntent = EnemyIntent(
        type: EnemyActionType.defend,
        value: armor,
        risk: risk,
        description: "Defends for $armor (${risk.name})",
        isSuccess: success,
        finalValue: armor,
      );

      // Apply defense immediately if successful
      if (success) {
        enemy.armor += armor;
        _addLog("Enemy prepares defense! (+$armor Armor)");
        _effectEventController.sink.add(
          EffectEvent(
            type: ActionType.defend,
            risk: risk,
            target: EffectTarget.enemy,
            feedbackType: null, // 방어 성공이므로 feedbackType 없음
          ),
        );
      } else {
        _addLog("Enemy tried to defend but fumbled!");
      }
    }
    notifyListeners();
  }
}
