import 'package:asciineverdie/data/skill_data.dart';
import 'package:asciineverdie/src/core/engine/skill_auto_selector.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';
import 'package:asciineverdie/src/core/util/roman.dart';

/// 스킬 시스템 서비스
///
/// 스킬 사용, 쿨타임 관리, MP 관리, 자동 스킬 선택 등을 담당
class SkillService {
  const SkillService({required this.rng});

  final DeterministicRandom rng;

  // ============================================================================
  // 스킬 사용 가능 여부 확인
  // ============================================================================

  /// 스킬 사용 가능 여부 확인
  SkillFailReason? canUseSkill({
    required Skill skill,
    required int currentMp,
    required SkillSystemState skillSystem,
  }) {
    // GCD 체크 (글로벌 쿨타임 1500ms)
    if (skillSystem.isGlobalCooldownActive) {
      return SkillFailReason.onGlobalCooldown;
    }

    // MP 체크
    if (currentMp < skill.mpCost) {
      return SkillFailReason.notEnoughMp;
    }

    // 쿨타임 체크
    final skillState = skillSystem.getSkillState(skill.id);
    if (skillState != null &&
        !skillState.isReady(skillSystem.elapsedMs, skill.cooldownMs)) {
      return SkillFailReason.onCooldown;
    }

    return null; // 사용 가능
  }

  // ============================================================================
  // 스킬 사용
  // ============================================================================

  /// 공격 스킬 사용
  ///
  /// Returns: (결과, 업데이트된 플레이어 스탯, 업데이트된 몬스터 스탯)
  ({
    SkillUseResult result,
    CombatStats updatedPlayer,
    MonsterCombatStats updatedMonster,
    SkillSystemState updatedSkillSystem,
  })
  useAttackSkill({
    required Skill skill,
    required CombatStats player,
    required MonsterCombatStats monster,
    required SkillSystemState skillSystem,
  }) {
    // 데미지 타입에 따른 공격력/방어력 선택
    final (attackStat, defenseStat) = _getStatsByDamageType(
      skill.damageType,
      player,
      monster,
    );

    // 기본 데미지 계산
    final baseDamage = attackStat * skill.damageMultiplier;

    // 버프 효과 적용
    final buffMods = skillSystem.totalBuffModifiers;
    final buffedDamage = baseDamage * (1 + buffMods.atkMod);

    // 적 방어력 감소 적용
    final effectiveMonsterDef = defenseStat * (1 - skill.targetDefReduction);

    // 최종 데미지 계산 (방어력 감산 0.3)
    final finalDamage = (buffedDamage - effectiveMonsterDef * 0.3)
        .round()
        .clamp(1, 9999);

    // 몬스터에 데미지 적용
    var updatedMonster = monster.applyDamage(finalDamage);

    // 자해 데미지 적용
    var updatedPlayer = player;
    if (skill.selfDamagePercent > 0) {
      final selfDamage = (player.hpMax * skill.selfDamagePercent).round();
      updatedPlayer = player.applyDamage(selfDamage);
    }

    // MP 소모
    updatedPlayer = updatedPlayer.withMp(
      updatedPlayer.mpCurrent - skill.mpCost,
    );

    // 스킬 상태 업데이트 (쿨타임 시작)
    final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);

    return (
      result: SkillUseResult(skill: skill, success: true, damage: finalDamage),
      updatedPlayer: updatedPlayer,
      updatedMonster: updatedMonster,
      updatedSkillSystem: updatedSkillSystem,
    );
  }

  /// 회복 스킬 사용
  ///
  /// [healingMultiplier] 회복력 배율 (기본 1.0, 클래스 패시브 적용)
  ({
    SkillUseResult result,
    CombatStats updatedPlayer,
    SkillSystemState updatedSkillSystem,
  })
  useHealSkill({
    required Skill skill,
    required CombatStats player,
    required SkillSystemState skillSystem,
    double healingMultiplier = 1.0,
  }) {
    // 회복량 계산
    int healAmount = skill.healAmount;
    if (skill.healPercent > 0) {
      healAmount += (player.hpMax * skill.healPercent).round();
    }

    // 회복력 보너스 적용 (예: Debugger Paladin +10%, Exception Handler +15%)
    healAmount = (healAmount * healingMultiplier).round();

    // HP 회복
    var updatedPlayer = player.applyHeal(healAmount);

    // MP 소모
    updatedPlayer = updatedPlayer.withMp(
      updatedPlayer.mpCurrent - skill.mpCost,
    );

    // 스킬 상태 업데이트
    final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);

    return (
      result: SkillUseResult(
        skill: skill,
        success: true,
        healedAmount: healAmount,
      ),
      updatedPlayer: updatedPlayer,
      updatedSkillSystem: updatedSkillSystem,
    );
  }

  /// 버프 스킬 사용
  ({
    SkillUseResult result,
    CombatStats updatedPlayer,
    SkillSystemState updatedSkillSystem,
  })
  useBuffSkill({
    required Skill skill,
    required CombatStats player,
    required SkillSystemState skillSystem,
  }) {
    if (skill.buff == null) {
      return (
        result: SkillUseResult.failed(skill, SkillFailReason.invalidState),
        updatedPlayer: player,
        updatedSkillSystem: skillSystem,
      );
    }

    // 버프 적용
    final newBuff = ActiveBuff(
      effect: skill.buff!,
      startedMs: skillSystem.elapsedMs,
      sourceSkillId: skill.id,
    );

    // 기존 같은 버프 제거 후 새 버프 추가
    final updatedBuffs =
        skillSystem.activeBuffs
            .where((b) => b.effect.id != skill.buff!.id)
            .toList()
          ..add(newBuff);

    // MP 소모
    var updatedPlayer = player.withMp(player.mpCurrent - skill.mpCost);

    // 스킬 상태 업데이트
    var updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);
    updatedSkillSystem = updatedSkillSystem.copyWith(activeBuffs: updatedBuffs);

    return (
      result: SkillUseResult(skill: skill, success: true, appliedBuff: newBuff),
      updatedPlayer: updatedPlayer,
      updatedSkillSystem: updatedSkillSystem,
    );
  }

  /// 디버프 스킬 사용
  ///
  /// 디버프 효과를 생성하여 반환. 호출자가 CombatState.activeDebuffs에 추가해야 함.
  /// 디버프는 몬스터의 ATK/DEF를 감소시킴.
  ({
    SkillUseResult result,
    CombatStats updatedPlayer,
    SkillSystemState updatedSkillSystem,
    ActiveBuff? debuffEffect,
  })
  useDebuffSkill({
    required Skill skill,
    required CombatStats player,
    required SkillSystemState skillSystem,
    required List<ActiveBuff> currentDebuffs,
  }) {
    if (skill.buff == null) {
      return (
        result: SkillUseResult.failed(skill, SkillFailReason.invalidState),
        updatedPlayer: player,
        updatedSkillSystem: skillSystem,
        debuffEffect: null,
      );
    }

    // 디버프 효과 생성
    final newDebuff = ActiveBuff(
      effect: skill.buff!,
      startedMs: skillSystem.elapsedMs,
      sourceSkillId: skill.id,
    );

    // MP 소모
    var updatedPlayer = player.withMp(player.mpCurrent - skill.mpCost);

    // 스킬 상태 업데이트 (쿨타임 시작)
    final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);

    return (
      result: SkillUseResult(
        skill: skill,
        success: true,
        appliedBuff: newDebuff,
      ),
      updatedPlayer: updatedPlayer,
      updatedSkillSystem: updatedSkillSystem,
      debuffEffect: newDebuff,
    );
  }

  /// DOT 스킬 사용
  ///
  /// DOT 효과를 생성하여 반환. 호출자가 전투 상태의 activeDoTs에 추가해야 함.
  /// INT → 틱당 데미지 보정, WIS → 틱 간격 보정
  ({
    SkillUseResult result,
    CombatStats updatedPlayer,
    SkillSystemState updatedSkillSystem,
    DotEffect? dotEffect,
  })
  useDotSkill({
    required Skill skill,
    required CombatStats player,
    required SkillSystemState skillSystem,
    required int playerInt,
    required int playerWis,
  }) {
    if (!skill.isDot) {
      return (
        result: SkillUseResult.failed(skill, SkillFailReason.invalidState),
        updatedPlayer: player,
        updatedSkillSystem: skillSystem,
        dotEffect: null,
      );
    }

    // DOT 효과 생성 (INT/WIS 보정 적용)
    final dotEffect = DotEffect.fromSkill(
      skill,
      playerInt: playerInt,
      playerWis: playerWis,
    );

    // MP 소모
    var updatedPlayer = player.withMp(player.mpCurrent - skill.mpCost);

    // 스킬 상태 업데이트 (쿨타임 시작)
    final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);

    // 예상 총 데미지 계산 (틱 수 × 틱당 데미지)
    final expectedTicks = dotEffect.totalDurationMs ~/ dotEffect.tickIntervalMs;
    final expectedDamage = expectedTicks * dotEffect.damagePerTick;

    return (
      result: SkillUseResult(
        skill: skill,
        success: true,
        damage: expectedDamage,
      ),
      updatedPlayer: updatedPlayer,
      updatedSkillSystem: updatedSkillSystem,
      dotEffect: dotEffect,
    );
  }

  // ============================================================================
  // 자동 스킬 선택 (SkillAutoSelector에 위임)
  // ============================================================================

  /// 전투 중 자동 스킬 선택
  ///
  /// 세부 로직은 SkillAutoSelector에 위임.
  Skill? selectAutoSkill({
    required CombatStats player,
    required MonsterCombatStats monster,
    required SkillSystemState skillSystem,
    required List<String> availableSkillIds,
    List<DotEffect> activeDoTs = const [],
    List<ActiveBuff> activeDebuffs = const [],
  }) {
    final selector = SkillAutoSelector(rng: rng);
    return selector.selectAutoSkill(
      player: player,
      monster: monster,
      skillSystem: skillSystem,
      availableSkillIds: availableSkillIds,
      canUse: (skill) =>
          canUseSkill(
            skill: skill,
            currentMp: player.mpCurrent,
            skillSystem: skillSystem,
          ) ==
          null,
      activeDoTs: activeDoTs,
      activeDebuffs: activeDebuffs,
    );
  }

  // ============================================================================
  // MP 회복
  // ============================================================================

  /// MP 자연 회복
  ///
  /// [elapsedMs] 경과 시간 (밀리초)
  /// [isInCombat] 전투 중 여부
  /// [wis] 지혜 스탯 (회복 속도 보정)
  int calculateMpRegen({
    required int elapsedMs,
    required bool isInCombat,
    required int wis,
  }) {
    if (isInCombat) {
      // 전투 중: WIS에 비례한 느린 회복 (500ms당 1 + WIS/20)
      final regenPerTick = 1 + wis ~/ 20;
      return (elapsedMs ~/ 500) * regenPerTick;
    } else {
      // 비전투: 50ms당 1 회복
      return elapsedMs ~/ 50;
    }
  }

  // ============================================================================
  // 버프 관리
  // ============================================================================

  /// 만료된 버프 제거
  SkillSystemState cleanupExpiredBuffs(SkillSystemState state) {
    final activeBuffs = state.activeBuffs
        .where((b) => !b.isExpired(state.elapsedMs))
        .toList();

    return state.copyWith(activeBuffs: activeBuffs);
  }

  // ============================================================================
  // 유틸리티
  // ============================================================================

  /// 스킬 쿨타임 업데이트
  SkillSystemState _updateSkillCooldown(
    SkillSystemState state,
    String skillId,
  ) {
    final skillStates = List<SkillState>.from(state.skillStates);

    // 기존 상태 찾기
    final existingIndex = skillStates.indexWhere((s) => s.skillId == skillId);

    if (existingIndex >= 0) {
      // 기존 상태 업데이트
      skillStates[existingIndex] = skillStates[existingIndex].copyWith(
        lastUsedMs: state.elapsedMs,
      );
    } else {
      // 새 상태 추가
      skillStates.add(
        SkillState(skillId: skillId, lastUsedMs: state.elapsedMs, rank: 1),
      );
    }

    return state.copyWith(skillStates: skillStates);
  }

  /// 스킬 시스템 시간 업데이트
  SkillSystemState updateElapsedTime(SkillSystemState state, int deltaMs) {
    return state.copyWith(elapsedMs: state.elapsedMs + deltaMs);
  }

  // ============================================================================
  // SkillBook 연동
  // ============================================================================

  /// SkillBook에서 사용 가능한 스킬 목록 조회
  ///
  /// SkillEntry 이름을 Skill로 매핑하여 반환
  List<Skill> getAvailableSkillsFromSkillBook(SkillBook skillBook) {
    return skillBook.skills
        .map((entry) => SkillData.getSkillBySpellName(entry.name))
        .whereType<Skill>()
        .toList();
  }

  /// SkillBook에서 스킬의 랭크(레벨) 조회
  ///
  /// 로마숫자 랭크(I, II, III)를 정수로 변환하여 반환
  /// 스킬이 없으면 1 반환
  int getSkillRankFromSkillBook(SkillBook skillBook, String skillId) {
    // skillId로 스킬 찾기
    final skill = SkillData.getSkillById(skillId);
    if (skill == null) return 1;

    // 스킬 이름으로 SkillEntry 찾기
    for (final entry in skillBook.skills) {
      if (entry.name == skill.name) {
        return romanToInt(entry.rank);
      }
    }

    return 1; // 기본 랭크
  }

  /// SkillBook에서 스킬 ID 목록 조회
  ///
  /// 전투 시스템에서 사용 가능한 스킬 ID 목록 반환
  List<String> getAvailableSkillIdsFromSkillBook(SkillBook skillBook) {
    return getAvailableSkillsFromSkillBook(
      skillBook,
    ).map((skill) => skill.id).toList();
  }

  /// 랭크 스케일링이 적용된 공격 스킬 사용
  ///
  /// [rank] 스펠 랭크 (SkillBook에서 조회)
  ({
    SkillUseResult result,
    CombatStats updatedPlayer,
    MonsterCombatStats updatedMonster,
    SkillSystemState updatedSkillSystem,
  })
  useAttackSkillWithRank({
    required Skill skill,
    required CombatStats player,
    required MonsterCombatStats monster,
    required SkillSystemState skillSystem,
    required int rank,
  }) {
    // 랭크 스케일링 적용
    final rankMult = getRankMultiplier(rank);
    final mpMult = getRankMpMultiplier(rank);

    // 실제 MP 비용 계산
    final actualMpCost = (skill.mpCost * mpMult).round();

    // 데미지 타입에 따른 공격력/방어력 선택
    final (attackStat, defenseStat) = _getStatsByDamageType(
      skill.damageType,
      player,
      monster,
    );

    // 기본 데미지 계산 (랭크 배율 적용)
    final baseDamage = attackStat * skill.damageMultiplier * rankMult;

    // 버프 효과 적용
    final buffMods = skillSystem.totalBuffModifiers;
    final buffedDamage = baseDamage * (1 + buffMods.atkMod);

    // 적 방어력 감소 적용
    final effectiveMonsterDef = defenseStat * (1 - skill.targetDefReduction);

    // 최종 데미지 계산 (방어력 감산 0.3)
    final finalDamage = (buffedDamage - effectiveMonsterDef * 0.3)
        .round()
        .clamp(1, 9999);

    // 몬스터에 데미지 적용
    var updatedMonster = monster.applyDamage(finalDamage);

    // 자해 데미지 적용
    var updatedPlayer = player;
    if (skill.selfDamagePercent > 0) {
      final selfDamage = (player.hpMax * skill.selfDamagePercent).round();
      updatedPlayer = player.applyDamage(selfDamage);
    }

    // MP 소모 (랭크 스케일링 적용)
    updatedPlayer = updatedPlayer.withMp(
      updatedPlayer.mpCurrent - actualMpCost,
    );

    // 스킬 상태 업데이트 (쿨타임 시작, 랭크 저장)
    // 쿨타임 스케일링은 isReady 체크 시 적용됨
    final updatedSkillSystem = _updateSkillCooldownWithRank(
      skillSystem,
      skill.id,
      rank,
    );

    return (
      result: SkillUseResult(skill: skill, success: true, damage: finalDamage),
      updatedPlayer: updatedPlayer,
      updatedMonster: updatedMonster,
      updatedSkillSystem: updatedSkillSystem,
    );
  }

  /// 랭크 정보를 포함한 스킬 쿨타임 업데이트
  SkillSystemState _updateSkillCooldownWithRank(
    SkillSystemState state,
    String skillId,
    int rank,
  ) {
    final skillStates = List<SkillState>.from(state.skillStates);

    // 기존 상태 찾기
    final existingIndex = skillStates.indexWhere((s) => s.skillId == skillId);

    if (existingIndex >= 0) {
      // 기존 상태 업데이트
      skillStates[existingIndex] = skillStates[existingIndex].copyWith(
        lastUsedMs: state.elapsedMs,
        rank: rank,
      );
    } else {
      // 새 상태 추가
      skillStates.add(
        SkillState(skillId: skillId, lastUsedMs: state.elapsedMs, rank: rank),
      );
    }

    return state.copyWith(skillStates: skillStates);
  }

  // ============================================================================
  // 데미지 타입 헬퍼
  // ============================================================================

  /// 데미지 타입에 따른 공격력/방어력 스탯 반환
  ///
  /// [damageType] 스킬의 데미지 타입
  /// [player] 플레이어 전투 스탯
  /// [monster] 몬스터 전투 스탯
  /// Returns: (공격력, 방어력) 튜플
  (double, double) _getStatsByDamageType(
    DamageType damageType,
    CombatStats player,
    MonsterCombatStats monster,
  ) {
    return switch (damageType) {
      DamageType.physical => (player.atk.toDouble(), monster.def.toDouble()),
      DamageType.magical => (
        player.magAtk.toDouble(),
        monster.magDef.toDouble(),
      ),
      DamageType.hybrid => (
        (player.atk + player.magAtk) / 2,
        (monster.def + monster.magDef) / 2,
      ),
    };
  }
}
