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/combat_event.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/monster_combat_stats.dart';
import 'package:asciineverdie/src/core/model/skill.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';

/// 플레이어 공격 처리 결과
typedef PlayerAttackResult = ({
  CombatStats playerStats,
  MonsterCombatStats monsterStats,
  SkillSystemState skillSystem,
  List<DotEffect> activeDoTs,
  List<ActiveBuff> activeDebuffs,
  int totalDamageDealt,
  List<CombatEvent> events,
  bool isFirstPlayerAttack,
});

/// 플레이어 공격 처리 서비스
///
/// CombatTickService에서 분리된 플레이어 공격 로직 담당:
/// - 스킬 자동 선택 및 사용
/// - 일반 공격 처리
/// - 첫 공격 보너스
/// - 연속 공격 (Multi-attack)
class PlayerAttackProcessor {
  PlayerAttackProcessor({required this.rng});

  final DeterministicRandom rng;

  /// 플레이어 공격 처리
  PlayerAttackResult processAttack({
    required GameState state,
    required CombatStats playerStats,
    required MonsterCombatStats monsterStats,
    required SkillSystemState updatedSkillSystem,
    required List<DotEffect> activeDoTs,
    required List<ActiveBuff> activeDebuffs,
    required int totalDamageDealt,
    required int timestamp,
    required CombatCalculator calculator,
    required SkillService skillService,
    required bool isFirstPlayerAttack,
    required double firstStrikeBonus,
    required bool hasMultiAttack,
    double healingMultiplier = 1.0,
  }) {
    final events = <CombatEvent>[];
    var newPlayerStats = playerStats;
    var newMonsterStats = monsterStats;
    var newSkillSystem = updatedSkillSystem;
    var newActiveDoTs = [...activeDoTs];
    var newActiveBuffs = [...activeDebuffs];
    var newTotalDamageDealt = totalDamageDealt;

    // 장착된 스킬 슬롯에서 사용 가능한 스킬 ID 목록 조회
    var availableSkillIds = state.skillSystem.equippedSkills.allSkills
        .map((s) => s.id)
        .toList();
    // 장착된 스킬이 없으면 기본 스킬 사용
    if (availableSkillIds.isEmpty) {
      availableSkillIds = SkillData.defaultSkillIds;
    }

    final selectedSkill = skillService.selectAutoSkill(
      player: newPlayerStats,
      monster: newMonsterStats,
      skillSystem: newSkillSystem,
      availableSkillIds: availableSkillIds,
      activeDoTs: newActiveDoTs,
      activeDebuffs: newActiveBuffs,
    );

    if (selectedSkill != null && selectedSkill.isAttack) {
      final result = _useAttackSkill(
        state: state,
        skill: selectedSkill,
        playerStats: newPlayerStats,
        monsterStats: newMonsterStats,
        skillSystem: newSkillSystem,
        skillService: skillService,
        timestamp: timestamp,
      );
      newPlayerStats = result.playerStats;
      newMonsterStats = result.monsterStats;
      newTotalDamageDealt += result.damage;
      newSkillSystem = result.skillSystem;
      events.add(result.event);
    } else if (selectedSkill != null && selectedSkill.isDot) {
      final result = _useDotSkill(
        state: state,
        skill: selectedSkill,
        playerStats: newPlayerStats,
        skillSystem: newSkillSystem,
        skillService: skillService,
        monsterName: newMonsterStats.name,
        timestamp: timestamp,
      );
      newPlayerStats = result.playerStats;
      newSkillSystem = result.skillSystem;
      if (result.dotEffect != null) newActiveDoTs.add(result.dotEffect!);
      events.add(result.event);
    } else if (selectedSkill != null && selectedSkill.isHeal) {
      final result = _useHealSkill(
        skill: selectedSkill,
        playerStats: newPlayerStats,
        skillSystem: newSkillSystem,
        skillService: skillService,
        healingMultiplier: healingMultiplier,
        timestamp: timestamp,
      );
      newPlayerStats = result.playerStats;
      newSkillSystem = result.skillSystem;
      events.add(result.event);
    } else if (selectedSkill != null && selectedSkill.isBuff) {
      final result = skillService.useBuffSkill(
        skill: selectedSkill,
        player: newPlayerStats,
        skillSystem: newSkillSystem,
      );
      newPlayerStats = result.updatedPlayer;
      newSkillSystem = result.updatedSkillSystem.startGlobalCooldown();
      events.add(
        CombatEvent.playerBuff(
          timestamp: timestamp,
          skillName: selectedSkill.name,
        ),
      );
    } else if (selectedSkill != null && selectedSkill.isDebuff) {
      final result = _useDebuffSkill(
        skill: selectedSkill,
        playerStats: newPlayerStats,
        skillSystem: newSkillSystem,
        skillService: skillService,
        activeDebuffs: newActiveBuffs,
        monsterName: newMonsterStats.name,
        timestamp: timestamp,
      );
      newPlayerStats = result.playerStats;
      newSkillSystem = result.skillSystem;
      newActiveBuffs = result.activeDebuffs;
      events.add(result.event);
    } else {
      // 일반 공격
      final result = _processNormalAttack(
        playerStats: newPlayerStats,
        monsterStats: newMonsterStats,
        calculator: calculator,
        isFirstPlayerAttack: isFirstPlayerAttack,
        firstStrikeBonus: firstStrikeBonus,
        hasMultiAttack: hasMultiAttack,
        timestamp: timestamp,
      );
      newMonsterStats = result.monsterStats;
      newTotalDamageDealt += result.totalDamage;
      events.addAll(result.events);
    }

    return (
      playerStats: newPlayerStats,
      monsterStats: newMonsterStats,
      skillSystem: newSkillSystem,
      activeDoTs: newActiveDoTs,
      activeDebuffs: newActiveBuffs,
      totalDamageDealt: newTotalDamageDealt,
      events: events,
      isFirstPlayerAttack: false,
    );
  }

  // ============================================================================
  // 스킬 사용 헬퍼
  // ============================================================================

  ({
    CombatStats playerStats,
    MonsterCombatStats monsterStats,
    int damage,
    SkillSystemState skillSystem,
    CombatEvent event,
  })
  _useAttackSkill({
    required GameState state,
    required Skill skill,
    required CombatStats playerStats,
    required MonsterCombatStats monsterStats,
    required SkillSystemState skillSystem,
    required SkillService skillService,
    required int timestamp,
  }) {
    final skillRank = skillService.getSkillRankFromSkillBook(
      state.skillBook,
      skill.id,
    );
    final skillResult = skillService.useAttackSkillWithRank(
      skill: skill,
      player: playerStats,
      monster: monsterStats,
      skillSystem: skillSystem,
      rank: skillRank,
    );
    return (
      playerStats: skillResult.updatedPlayer,
      monsterStats: skillResult.updatedMonster,
      damage: skillResult.result.damage,
      skillSystem: skillResult.updatedSkillSystem.startGlobalCooldown(),
      event: CombatEvent.playerSkill(
        timestamp: timestamp,
        skillName: skill.name,
        damage: skillResult.result.damage,
        targetName: monsterStats.name,
        attackDelayMs: playerStats.attackDelayMs,
      ),
    );
  }

  ({
    CombatStats playerStats,
    SkillSystemState skillSystem,
    DotEffect? dotEffect,
    CombatEvent event,
  })
  _useDotSkill({
    required GameState state,
    required Skill skill,
    required CombatStats playerStats,
    required SkillSystemState skillSystem,
    required SkillService skillService,
    required String monsterName,
    required int timestamp,
  }) {
    final skillResult = skillService.useDotSkill(
      skill: skill,
      player: playerStats,
      skillSystem: skillSystem,
      playerInt: state.stats.intelligence,
      playerWis: state.stats.wis,
    );
    return (
      playerStats: skillResult.updatedPlayer,
      skillSystem: skillResult.updatedSkillSystem.startGlobalCooldown(),
      dotEffect: skillResult.dotEffect,
      event: CombatEvent.playerSkill(
        timestamp: timestamp,
        skillName: skill.name,
        damage: skillResult.result.damage,
        targetName: monsterName,
        attackDelayMs: playerStats.attackDelayMs,
      ),
    );
  }

  ({CombatStats playerStats, SkillSystemState skillSystem, CombatEvent event})
  _useHealSkill({
    required Skill skill,
    required CombatStats playerStats,
    required SkillSystemState skillSystem,
    required SkillService skillService,
    required double healingMultiplier,
    required int timestamp,
  }) {
    final skillResult = skillService.useHealSkill(
      skill: skill,
      player: playerStats,
      skillSystem: skillSystem,
      healingMultiplier: healingMultiplier,
    );
    return (
      playerStats: skillResult.updatedPlayer,
      skillSystem: skillResult.updatedSkillSystem.startGlobalCooldown(),
      event: CombatEvent.playerHeal(
        timestamp: timestamp,
        healAmount: skillResult.result.healedAmount,
        skillName: skill.name,
      ),
    );
  }

  ({
    CombatStats playerStats,
    SkillSystemState skillSystem,
    List<ActiveBuff> activeDebuffs,
    CombatEvent event,
  })
  _useDebuffSkill({
    required Skill skill,
    required CombatStats playerStats,
    required SkillSystemState skillSystem,
    required SkillService skillService,
    required List<ActiveBuff> activeDebuffs,
    required String monsterName,
    required int timestamp,
  }) {
    final skillResult = skillService.useDebuffSkill(
      skill: skill,
      player: playerStats,
      skillSystem: skillSystem,
      currentDebuffs: activeDebuffs,
    );
    var newDebuffs = activeDebuffs;
    if (skillResult.debuffEffect != null) {
      newDebuffs =
          activeDebuffs
              .where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
              .toList()
            ..add(skillResult.debuffEffect!);
    }
    return (
      playerStats: skillResult.updatedPlayer,
      skillSystem: skillResult.updatedSkillSystem.startGlobalCooldown(),
      activeDebuffs: newDebuffs,
      event: CombatEvent.playerDebuff(
        timestamp: timestamp,
        skillName: skill.name,
        targetName: monsterName,
      ),
    );
  }

  // ============================================================================
  // 일반 공격
  // ============================================================================

  ({MonsterCombatStats monsterStats, int totalDamage, List<CombatEvent> events})
  _processNormalAttack({
    required CombatStats playerStats,
    required MonsterCombatStats monsterStats,
    required CombatCalculator calculator,
    required bool isFirstPlayerAttack,
    required double firstStrikeBonus,
    required bool hasMultiAttack,
    required int timestamp,
  }) {
    final events = <CombatEvent>[];
    var newMonsterStats = monsterStats;
    var totalDamage = 0;

    final attackResult = calculator.playerAttackMonster(
      attacker: playerStats,
      defender: newMonsterStats,
    );
    newMonsterStats = attackResult.updatedDefender;

    // 첫 공격 배율 적용
    var damage = attackResult.result.damage;
    if (isFirstPlayerAttack && firstStrikeBonus > 1.0) {
      damage = (damage * firstStrikeBonus).round();
      final extraDamage = damage - attackResult.result.damage;
      if (extraDamage > 0) {
        final newHp = (newMonsterStats.hpCurrent - extraDamage).clamp(
          0,
          newMonsterStats.hpMax,
        );
        newMonsterStats = newMonsterStats.copyWith(hpCurrent: newHp);
      }
    }
    totalDamage += damage;

    final result = attackResult.result;
    if (result.isEvaded) {
      events.add(
        CombatEvent.monsterEvade(
          timestamp: timestamp,
          targetName: newMonsterStats.name,
        ),
      );
    } else {
      events.add(
        CombatEvent.playerAttack(
          timestamp: timestamp,
          damage: damage,
          targetName: newMonsterStats.name,
          isCritical: result.isCritical,
          attackDelayMs: playerStats.attackDelayMs,
        ),
      );
    }

    // 연속 공격 (Refactor Monk 패시브) - 30% 확률로 추가 공격
    if (hasMultiAttack && newMonsterStats.isAlive && rng.nextDouble() < 0.3) {
      final extraAttack = calculator.playerAttackMonster(
        attacker: playerStats,
        defender: newMonsterStats,
      );
      newMonsterStats = extraAttack.updatedDefender;
      totalDamage += extraAttack.result.damage;

      if (!extraAttack.result.isEvaded) {
        events.add(
          CombatEvent.playerAttack(
            timestamp: timestamp,
            damage: extraAttack.result.damage,
            targetName: newMonsterStats.name,
            isCritical: extraAttack.result.isCritical,
            attackDelayMs: playerStats.attackDelayMs,
          ),
        );
      }
    }

    return (
      monsterStats: newMonsterStats,
      totalDamage: totalDamage,
      events: events,
    );
  }
}
