import 'package:asciineverdie/data/skill_data.dart';
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
import 'package:asciineverdie/src/core/engine/skill_service.dart';
import 'package:asciineverdie/src/core/model/arena_match.dart';
import 'package:asciineverdie/src/core/model/combat_stats.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
import 'package:asciineverdie/src/core/model/skill.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';

/// 아레나 전투 시뮬레이터
///
/// ArenaService에서 분리된 전투 시뮬레이션 로직.
/// 스킬 시스템을 포함한 턴 기반 전투를 처리한다.
class ArenaCombatSimulator {
  ArenaCombatSimulator({required DeterministicRandom rng})
    : _rng = rng,
      _skillService = SkillService(rng: rng);

  final DeterministicRandom _rng;
  final SkillService _skillService;

  /// 전투 시뮬레이션 (애니메이션용 스트림)
  Stream<ArenaCombatTurn> simulateCombat(ArenaMatch match) async* {
    final challengerStats = match.challenger.finalStats;
    final opponentStats = match.opponent.finalStats;

    if (challengerStats == null || opponentStats == null) {
      return;
    }

    final calculator = CombatCalculator(rng: _rng);

    // 스킬 ID 목록 로드
    var challengerSkillIds = _getSkillIdsFromEntry(match.challenger);
    var opponentSkillIds = _getSkillIdsFromEntry(match.opponent);
    if (challengerSkillIds.isEmpty) {
      challengerSkillIds = SkillData.defaultSkillIds;
    }
    if (opponentSkillIds.isEmpty) {
      opponentSkillIds = SkillData.defaultSkillIds;
    }

    // 스킬 시스템 상태 초기화
    var challengerSkillSystem = SkillSystemState.empty();
    var opponentSkillSystem = SkillSystemState.empty();

    // DOT 및 디버프 추적
    var challengerDoTs = <DotEffect>[];
    var opponentDoTs = <DotEffect>[];
    var challengerDebuffs = <ActiveBuff>[];
    var opponentDebuffs = <ActiveBuff>[];

    var playerCombatStats = challengerStats.copyWith(
      hpCurrent: challengerStats.hpMax,
      mpCurrent: challengerStats.mpMax,
    );

    var opponentCombatStats = opponentStats.copyWith(
      hpCurrent: opponentStats.hpMax,
      mpCurrent: opponentStats.mpMax,
    );

    int playerAccum = 0;
    int opponentAccum = 0;
    int elapsedMs = 0;
    const tickMs = 200;
    int turns = 0;

    // 초기 상태 전송
    yield ArenaCombatTurn(
      challengerHp: playerCombatStats.hpCurrent,
      opponentHp: opponentCombatStats.hpCurrent,
      challengerHpMax: playerCombatStats.hpMax,
      opponentHpMax: opponentCombatStats.hpMax,
      challengerMp: playerCombatStats.mpCurrent,
      opponentMp: opponentCombatStats.mpCurrent,
      challengerMpMax: playerCombatStats.mpMax,
      opponentMpMax: opponentCombatStats.mpMax,
    );

    while (playerCombatStats.isAlive && opponentCombatStats.hpCurrent > 0) {
      playerAccum += tickMs;
      opponentAccum += tickMs;
      elapsedMs += tickMs;

      // 스킬 시스템 시간 업데이트
      challengerSkillSystem = challengerSkillSystem.copyWith(
        elapsedMs: elapsedMs,
      );
      opponentSkillSystem = opponentSkillSystem.copyWith(elapsedMs: elapsedMs);

      int? challengerDamage;
      int? opponentDamage;
      bool isChallengerCritical = false;
      bool isOpponentCritical = false;
      bool isChallengerEvaded = false;
      bool isOpponentEvaded = false;
      bool isChallengerBlocked = false;
      bool isOpponentBlocked = false;
      String? challengerSkillUsed;
      String? opponentSkillUsed;
      int? challengerHealAmount;
      int? opponentHealAmount;

      // DOT 틱 처리
      final dotResult = _processDotTicks(
        challengerDoTs: challengerDoTs,
        opponentDoTs: opponentDoTs,
        playerStats: playerCombatStats,
        opponentStats: opponentCombatStats,
        tickMs: tickMs,
      );
      challengerDoTs = dotResult.challengerDoTs;
      opponentDoTs = dotResult.opponentDoTs;
      playerCombatStats = dotResult.playerStats;
      opponentCombatStats = dotResult.opponentStats;

      // 만료된 디버프 정리
      challengerDebuffs = challengerDebuffs
          .where((ActiveBuff d) => !d.isExpired(elapsedMs))
          .toList();
      opponentDebuffs = opponentDebuffs
          .where((ActiveBuff d) => !d.isExpired(elapsedMs))
          .toList();

      // 도전자 턴
      if (playerAccum >= playerCombatStats.attackDelayMs) {
        playerAccum = 0;

        var opponentMonsterStats = MonsterCombatStats.fromCombatStats(
          opponentCombatStats,
          match.opponent.characterName,
        );

        final turnResult = _processCharacterTurn(
          player: playerCombatStats,
          target: opponentCombatStats,
          targetMonster: opponentMonsterStats,
          targetName: match.opponent.characterName,
          entry: match.challenger,
          skillIds: challengerSkillIds,
          skillSystem: challengerSkillSystem,
          activeDoTs: challengerDoTs,
          activeDebuffs: opponentDebuffs,
          calculator: calculator,
          elapsedMs: elapsedMs,
        );

        playerCombatStats = turnResult.player;
        opponentCombatStats = turnResult.target;
        challengerSkillSystem = turnResult.skillSystem;
        challengerDoTs = turnResult.activeDoTs;
        opponentDebuffs = turnResult.targetDebuffs;
        challengerDamage = turnResult.damage;
        isChallengerCritical = turnResult.isCritical;
        isOpponentEvaded = turnResult.isTargetEvaded;
        challengerSkillUsed = turnResult.skillUsed;
        challengerHealAmount = turnResult.healAmount;
      }

      // 상대 턴
      if (opponentCombatStats.hpCurrent > 0 &&
          opponentAccum >= opponentCombatStats.attackDelayMs) {
        opponentAccum = 0;

        var challengerMonsterStats = MonsterCombatStats.fromCombatStats(
          playerCombatStats,
          match.challenger.characterName,
        );

        final turnResult = _processCharacterTurn(
          player: opponentCombatStats,
          target: playerCombatStats,
          targetMonster: challengerMonsterStats,
          targetName: match.challenger.characterName,
          entry: match.opponent,
          skillIds: opponentSkillIds,
          skillSystem: opponentSkillSystem,
          activeDoTs: opponentDoTs,
          activeDebuffs: challengerDebuffs,
          calculator: calculator,
          elapsedMs: elapsedMs,
        );

        opponentCombatStats = turnResult.player;
        playerCombatStats = turnResult.target;
        opponentSkillSystem = turnResult.skillSystem;
        opponentDoTs = turnResult.activeDoTs;
        challengerDebuffs = turnResult.targetDebuffs;
        opponentDamage = turnResult.damage;
        isOpponentCritical = turnResult.isCritical;
        isChallengerEvaded = turnResult.isTargetEvaded;
        isChallengerBlocked = turnResult.isTargetBlocked;
        opponentSkillUsed = turnResult.skillUsed;
        opponentHealAmount = turnResult.healAmount;
      }

      // 액션이 발생했을 때만 턴 전송
      final hasAction =
          challengerDamage != null ||
          opponentDamage != null ||
          challengerHealAmount != null ||
          opponentHealAmount != null ||
          challengerSkillUsed != null ||
          opponentSkillUsed != null;

      if (hasAction) {
        turns++;
        yield ArenaCombatTurn(
          challengerDamage: challengerDamage,
          opponentDamage: opponentDamage,
          challengerHp: playerCombatStats.hpCurrent,
          opponentHp: opponentCombatStats.hpCurrent,
          challengerHpMax: playerCombatStats.hpMax,
          opponentHpMax: opponentCombatStats.hpMax,
          challengerMp: playerCombatStats.mpCurrent,
          opponentMp: opponentCombatStats.mpCurrent,
          challengerMpMax: playerCombatStats.mpMax,
          opponentMpMax: opponentCombatStats.mpMax,
          isChallengerCritical: isChallengerCritical,
          isOpponentCritical: isOpponentCritical,
          isChallengerEvaded: isChallengerEvaded,
          isOpponentEvaded: isOpponentEvaded,
          isChallengerBlocked: isChallengerBlocked,
          isOpponentBlocked: isOpponentBlocked,
          challengerSkillUsed: challengerSkillUsed,
          opponentSkillUsed: opponentSkillUsed,
          challengerHealAmount: challengerHealAmount,
          opponentHealAmount: opponentHealAmount,
        );

        await Future<void>.delayed(const Duration(milliseconds: 100));
      }

      if (turns > 1000) break;
    }
  }

  /// DOT 틱 처리 (양측)
  ({
    List<DotEffect> challengerDoTs,
    List<DotEffect> opponentDoTs,
    CombatStats playerStats,
    CombatStats opponentStats,
  })
  _processDotTicks({
    required List<DotEffect> challengerDoTs,
    required List<DotEffect> opponentDoTs,
    required CombatStats playerStats,
    required CombatStats opponentStats,
    required int tickMs,
  }) {
    var updatedPlayerStats = playerStats;
    var updatedOpponentStats = opponentStats;

    // 도전자 -> 상대에게 적용된 DOT
    var dotDamageToOpponent = 0;
    final updatedChallengerDoTs = <DotEffect>[];
    for (final dot in challengerDoTs) {
      final (updatedDot, ticksTriggered) = dot.tick(tickMs);
      if (ticksTriggered > 0) {
        dotDamageToOpponent += dot.damagePerTick * ticksTriggered;
      }
      if (updatedDot.isActive) updatedChallengerDoTs.add(updatedDot);
    }
    if (dotDamageToOpponent > 0 && updatedOpponentStats.hpCurrent > 0) {
      updatedOpponentStats = updatedOpponentStats.copyWith(
        hpCurrent: (updatedOpponentStats.hpCurrent - dotDamageToOpponent).clamp(
          0,
          updatedOpponentStats.hpMax,
        ),
      );
    }

    // 상대 -> 도전자에게 적용된 DOT
    var dotDamageToChallenger = 0;
    final updatedOpponentDoTs = <DotEffect>[];
    for (final dot in opponentDoTs) {
      final (updatedDot, ticksTriggered) = dot.tick(tickMs);
      if (ticksTriggered > 0) {
        dotDamageToChallenger += dot.damagePerTick * ticksTriggered;
      }
      if (updatedDot.isActive) updatedOpponentDoTs.add(updatedDot);
    }
    if (dotDamageToChallenger > 0 && updatedPlayerStats.isAlive) {
      updatedPlayerStats = updatedPlayerStats.copyWith(
        hpCurrent: (updatedPlayerStats.hpCurrent - dotDamageToChallenger).clamp(
          0,
          updatedPlayerStats.hpMax,
        ),
      );
    }

    return (
      challengerDoTs: updatedChallengerDoTs,
      opponentDoTs: updatedOpponentDoTs,
      playerStats: updatedPlayerStats,
      opponentStats: updatedOpponentStats,
    );
  }

  /// 캐릭터 턴 처리 (도전자/상대 공통)
  ({
    CombatStats player,
    CombatStats target,
    SkillSystemState skillSystem,
    List<DotEffect> activeDoTs,
    List<ActiveBuff> targetDebuffs,
    int? damage,
    bool isCritical,
    bool isTargetEvaded,
    bool isTargetBlocked,
    String? skillUsed,
    int? healAmount,
  })
  _processCharacterTurn({
    required CombatStats player,
    required CombatStats target,
    required MonsterCombatStats targetMonster,
    required String targetName,
    required HallOfFameEntry entry,
    required List<String> skillIds,
    required SkillSystemState skillSystem,
    required List<DotEffect> activeDoTs,
    required List<ActiveBuff> activeDebuffs,
    required CombatCalculator calculator,
    required int elapsedMs,
  }) {
    int? damage;
    bool isCritical = false;
    bool isTargetEvaded = false;
    bool isTargetBlocked = false;
    String? skillUsed;
    int? healAmount;
    var updatedPlayer = player;
    var updatedTarget = target;
    var updatedSkillSystem = skillSystem;
    var updatedDoTs = [...activeDoTs];
    var updatedDebuffs = [...activeDebuffs];

    final selectedSkill = _skillService.selectAutoSkill(
      player: updatedPlayer,
      monster: targetMonster,
      skillSystem: updatedSkillSystem,
      availableSkillIds: skillIds,
      activeDoTs: updatedDoTs,
      activeDebuffs: updatedDebuffs,
    );

    if (selectedSkill != null && selectedSkill.isAttack) {
      final skillRank = _getSkillRankFromEntry(entry, selectedSkill.id);
      final skillResult = _skillService.useAttackSkillWithRank(
        skill: selectedSkill,
        player: updatedPlayer,
        monster: targetMonster,
        skillSystem: updatedSkillSystem,
        rank: skillRank,
      );
      updatedPlayer = skillResult.updatedPlayer;
      updatedTarget = updatedTarget.copyWith(
        hpCurrent: skillResult.updatedMonster.hpCurrent,
      );
      updatedSkillSystem = skillResult.updatedSkillSystem;
      skillUsed = selectedSkill.name;
      damage = skillResult.result.damage;
    } else if (selectedSkill != null && selectedSkill.isDot) {
      final skillResult = _skillService.useDotSkill(
        skill: selectedSkill,
        player: updatedPlayer,
        skillSystem: updatedSkillSystem,
        playerInt: updatedPlayer.intelligence,
        playerWis: updatedPlayer.wis,
      );
      updatedPlayer = skillResult.updatedPlayer;
      updatedSkillSystem = skillResult.updatedSkillSystem;
      if (skillResult.dotEffect != null) {
        updatedDoTs.add(skillResult.dotEffect!);
      }
      skillUsed = selectedSkill.name;
    } else if (selectedSkill != null && selectedSkill.isHeal) {
      final skillResult = _skillService.useHealSkill(
        skill: selectedSkill,
        player: updatedPlayer,
        skillSystem: updatedSkillSystem,
      );
      updatedPlayer = skillResult.updatedPlayer;
      updatedSkillSystem = skillResult.updatedSkillSystem;
      skillUsed = selectedSkill.name;
      healAmount = skillResult.result.healedAmount;
    } else if (selectedSkill != null && selectedSkill.isBuff) {
      final skillResult = _skillService.useBuffSkill(
        skill: selectedSkill,
        player: updatedPlayer,
        skillSystem: updatedSkillSystem,
      );
      updatedPlayer = skillResult.updatedPlayer;
      updatedSkillSystem = skillResult.updatedSkillSystem;
      skillUsed = selectedSkill.name;
    } else if (selectedSkill != null && selectedSkill.isDebuff) {
      final skillResult = _skillService.useDebuffSkill(
        skill: selectedSkill,
        player: updatedPlayer,
        skillSystem: updatedSkillSystem,
        currentDebuffs: updatedDebuffs,
      );
      updatedPlayer = skillResult.updatedPlayer;
      updatedSkillSystem = skillResult.updatedSkillSystem;
      final debuffEffect = skillResult.debuffEffect;
      if (debuffEffect != null) {
        updatedDebuffs =
            updatedDebuffs
                .where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id)
                .toList()
              ..add(debuffEffect);
      }
      skillUsed = selectedSkill.name;
    } else {
      // 일반 공격
      final opponentMonsterStats = MonsterCombatStats.fromCombatStats(
        updatedTarget,
        targetName,
      );
      final result = calculator.playerAttackMonster(
        attacker: updatedPlayer,
        defender: opponentMonsterStats,
      );
      updatedTarget = updatedTarget.copyWith(
        hpCurrent: result.updatedDefender.hpCurrent,
      );

      if (result.result.isHit) {
        damage = result.result.damage;
        isCritical = result.result.isCritical;
      } else {
        isTargetEvaded = true;
      }
    }

    return (
      player: updatedPlayer,
      target: updatedTarget,
      skillSystem: updatedSkillSystem,
      activeDoTs: updatedDoTs,
      targetDebuffs: updatedDebuffs,
      damage: damage,
      isCritical: isCritical,
      isTargetEvaded: isTargetEvaded,
      isTargetBlocked: isTargetBlocked,
      skillUsed: skillUsed,
      healAmount: healAmount,
    );
  }

  /// 스킬 ID 목록 추출 (HallOfFameEntry에서)
  List<String> _getSkillIdsFromEntry(HallOfFameEntry entry) {
    final skillData = entry.finalSkills;
    if (skillData == null || skillData.isEmpty) return [];

    final skillIds = <String>[];
    for (final data in skillData) {
      final skillName = data['name'];
      if (skillName != null) {
        final skill = SkillData.getSkillBySpellName(skillName);
        if (skill != null) {
          skillIds.add(skill.id);
        }
      }
    }
    return skillIds;
  }

  /// 스킬 랭크 조회 (HallOfFameEntry의 finalSkills에서)
  int _getSkillRankFromEntry(HallOfFameEntry entry, String skillId) {
    final skill = SkillData.getSkillById(skillId);
    if (skill == null) return 1;

    final skillData = entry.finalSkills;
    if (skillData == null || skillData.isEmpty) return 1;

    for (final data in skillData) {
      if (data['name'] == skill.name) {
        final rankStr = data['rank'] ?? 'I';
        return switch (rankStr) {
          'I' => 1,
          'II' => 2,
          'III' => 3,
          'IV' => 4,
          'V' => 5,
          _ => 1,
        };
      }
    }
    return 1;
  }
}
