import 'package:freezed_annotation/freezed_annotation.dart';

import 'package:asciineverdie/src/core/model/class_traits.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/race_traits.dart';

part 'combat_stats.freezed.dart';
part 'combat_stats.g.dart';

/// 전투용 파생 스탯
///
/// 기본 Stats와 Equipment를 기반으로 계산되는 전투 관련 수치.
/// 불변(immutable) 객체로 설계되어 상태 변경 시 새 인스턴스 생성.
@freezed
class CombatStats with _$CombatStats {
  // ============================================================================
  // 레벨 페널티 상수 (Phase 12)
  // ============================================================================

  /// 1레벨당 확률 감소율 (8%)
  static const double _levelPenaltyPerLevel = 0.08;

  /// 최저 페널티 배율 (20%)
  static const double _minLevelMultiplier = 0.2;

  // ============================================================================
  // 확률 캡 상수 (Phase 12)
  // ============================================================================

  /// 크리티컬 확률 최대 (50%)
  static const double _maxCriRate = 0.5;

  /// 회피율 최대 (40%)
  static const double _maxEvasion = 0.4;

  /// 방패 방어율 최대 (50%)
  static const double _maxBlockRate = 0.5;

  /// 무기 쳐내기 최대 (35%)
  static const double _maxParryRate = 0.35;

  // ============================================================================
  // 레벨 페널티 함수 (Phase 12)
  // ============================================================================

  /// 레벨 차이에 따른 확률 감소 배율 (플레이어 전용)
  static double _getLevelPenalty(int playerLevel, int monsterLevel) {
    final levelDiff = monsterLevel - playerLevel;
    if (levelDiff <= 0) return 1.0;
    final penalty = 1.0 - (levelDiff * _levelPenaltyPerLevel);
    return penalty.clamp(_minLevelMultiplier, 1.0);
  }

  const CombatStats._();

  const factory CombatStats({
    // 기본 스탯
    /// 힘: 물리 공격력 보정
    required int str,

    /// 체력: HP, 방어력 보정
    required int con,

    /// 민첩: 회피율, 크리티컬율, 명중률, 공격 속도
    required int dex,

    /// 지능: 마법 공격력, MP
    required int intelligence,

    /// 지혜: 마법 방어력, MP 회복
    required int wis,

    /// 매력: 상점 가격, 드롭률 보정
    required int cha,
    // 파생 스탯 (전투용)
    /// 물리 공격력
    required int atk,

    /// 물리 방어력
    required int def,

    /// 마법 공격력
    required int magAtk,

    /// 마법 방어력
    required int magDef,

    /// 크리티컬 확률 (0.0 ~ 1.0)
    required double criRate,

    /// 크리티컬 데미지 배율 (1.5 ~ 3.0)
    required double criDamage,

    /// 회피율 (0.0 ~ 0.5)
    required double evasion,

    /// 명중률 (0.8 ~ 1.0)
    required double accuracy,

    /// 방패 방어율 (0.0 ~ 0.4)
    required double blockRate,

    /// 무기로 쳐내기 확률 (0.0 ~ 0.3)
    required double parryRate,

    /// 공격 딜레이 (밀리초)
    required int attackDelayMs,
    // 자원
    /// 최대 HP
    required int hpMax,

    /// 현재 HP
    required int hpCurrent,

    /// 최대 MP
    required int mpMax,

    /// 현재 MP
    required int mpCurrent,
  }) = _CombatStats;

  factory CombatStats.fromJson(Map<String, dynamic> json) =>
      _$CombatStatsFromJson(json);

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

  /// HP 비율 (0.0 ~ 1.0)
  double get hpRatio => hpMax > 0 ? hpCurrent / hpMax : 0.0;

  /// MP 비율 (0.0 ~ 1.0)
  double get mpRatio => mpMax > 0 ? mpCurrent / mpMax : 0.0;

  /// 생존 여부
  bool get isAlive => hpCurrent > 0;

  /// 사망 여부
  bool get isDead => hpCurrent <= 0;

  /// HP 변경된 새 인스턴스 반환
  CombatStats withHp(int newHp) {
    return copyWith(hpCurrent: newHp.clamp(0, hpMax));
  }

  /// MP 변경된 새 인스턴스 반환
  CombatStats withMp(int newMp) {
    return copyWith(mpCurrent: newMp.clamp(0, mpMax));
  }

  /// 데미지 적용된 새 인스턴스 반환
  CombatStats applyDamage(int damage) {
    return withHp(hpCurrent - damage);
  }

  /// 힐 적용된 새 인스턴스 반환
  CombatStats applyHeal(int amount) {
    return withHp(hpCurrent + amount);
  }

  // ============================================================================
  // 팩토리 메서드
  // ============================================================================

  /// Stats와 Equipment에서 CombatStats 생성
  factory CombatStats.fromStats({
    required Stats stats,
    required Equipment equipment,
    required int level,
    RaceTraits? race,
    ClassTraits? klass,
    int? monsterLevel,
  }) {
    // 레벨 페널티 계산 (Phase 12)
    final levelPenalty = _getLevelPenalty(level, monsterLevel ?? level);
    // 장비 총 스탯 가져오기
    final equipStats = equipment.totalStats;

    // 종족/클래스 스탯 보정 적용
    final raceStr = race?.getModifier(StatType.str) ?? 0;
    final raceCon = race?.getModifier(StatType.con) ?? 0;
    final raceDex = race?.getModifier(StatType.dex) ?? 0;
    final raceInt = race?.getModifier(StatType.intelligence) ?? 0;
    final raceWis = race?.getModifier(StatType.wis) ?? 0;
    final raceCha = race?.getModifier(StatType.cha) ?? 0;

    final classStr = klass?.getModifier(StatType.str) ?? 0;
    final classCon = klass?.getModifier(StatType.con) ?? 0;
    final classDex = klass?.getModifier(StatType.dex) ?? 0;
    final classInt = klass?.getModifier(StatType.intelligence) ?? 0;
    final classWis = klass?.getModifier(StatType.wis) ?? 0;
    final classCha = klass?.getModifier(StatType.cha) ?? 0;

    // 장비 보너스 + 종족/클래스 보정이 적용된 기본 스탯
    final effectiveStr = stats.str + equipStats.strBonus + raceStr + classStr;
    final effectiveCon = stats.con + equipStats.conBonus + raceCon + classCon;
    final effectiveDex = stats.dex + equipStats.dexBonus + raceDex + classDex;
    final effectiveInt =
        stats.intelligence + equipStats.intBonus + raceInt + classInt;
    final effectiveWis = stats.wis + equipStats.wisBonus + raceWis + classWis;
    final effectiveCha = stats.cha + equipStats.chaBonus + raceCha + classCha;

    // 기본 공격력: STR 기반 + 레벨 보정 + 장비 ATK
    var baseAtk = effectiveStr * 2 + level + equipStats.atk;

    // 기본 방어력: CON 기반 + 레벨 보정 + 장비 DEF
    var baseDef = effectiveCon + (level ~/ 2) + equipStats.def;

    // 마법 공격력: INT 기반 + 장비 MAG_ATK
    var baseMagAtk = effectiveInt * 2 + level + equipStats.magAtk;

    // 마법 방어력: WIS 기반 + 장비 MAG_DEF
    final baseMagDef = effectiveWis + (level ~/ 2) + equipStats.magDef;

    // 크리티컬 확률: DEX 기반 + 장비 보너스 (0.05 ~ 0.8)
    var criRate = 0.05 + effectiveDex * 0.005 + equipStats.criRate;

    // 크리티컬 데미지: 기본 1.5배, DEX에 따라 증가 (최대 3.0)
    final criDamage = (1.5 + effectiveDex * 0.01).clamp(1.5, 3.0);

    // 회피율: DEX 기반 + 장비 보너스 (0.0 ~ 0.6)
    var evasion = effectiveDex * 0.005 + equipStats.evasion;

    // 명중률: DEX 기반 (0.8 ~ 1.0)
    final accuracy = (0.8 + effectiveDex * 0.002).clamp(0.8, 1.0);

    // 방패 방어율: 방패 장착 시 기본 + CON 보정 + 장비 보너스 (0.0 ~ 0.5)
    final hasShield = equipment.shield.isNotEmpty;
    final baseBlockRate = hasShield ? (0.1 + effectiveCon * 0.003) : 0.0;
    final blockRate = (baseBlockRate + equipStats.blockRate).clamp(0.0, 0.5);

    // 무기 쳐내기: DEX + STR 기반 + 장비 보너스 (0.0 ~ 0.4)
    final baseParryRate = (effectiveDex + effectiveStr) * 0.002;
    final parryRate = (baseParryRate + equipStats.parryRate).clamp(0.0, 0.4);

    // 공격 속도: 무기 기본 공속 + DEX 보정
    final weaponItem = equipment.items[0]; // 무기 슬롯
    final weaponSpeed = weaponItem.stats.attackSpeed;
    final baseAttackSpeed = weaponSpeed > 0 ? weaponSpeed : 1000;
    final speedModifier = 1.0 + (effectiveDex - 10) * 0.02;
    final attackDelayMs = (baseAttackSpeed / speedModifier).round().clamp(
      300,
      2000,
    );

    // HP/MP: 기본 + 장비 보너스
    var totalHpMax = stats.hpMax + equipStats.hpBonus;
    var totalMpMax = stats.mpMax + equipStats.mpBonus;

    // ========================================================================
    // 종족 패시브 적용 (Phase 5)
    // ========================================================================

    final raceHpBonus = race?.getPassiveValue(PassiveType.hpBonus) ?? 0.0;
    if (raceHpBonus > 0) {
      totalHpMax = (totalHpMax * (1 + raceHpBonus)).round();
    }

    final raceMpBonus = race?.getPassiveValue(PassiveType.mpBonus) ?? 0.0;
    if (raceMpBonus > 0) {
      totalMpMax = (totalMpMax * (1 + raceMpBonus)).round();
    }

    final raceMagicBonus =
        race?.getPassiveValue(PassiveType.magicDamageBonus) ?? 0.0;
    if (raceMagicBonus > 0) {
      baseMagAtk = (baseMagAtk * (1 + raceMagicBonus)).round();
    }

    final raceDefBonus = race?.getPassiveValue(PassiveType.defenseBonus) ?? 0.0;
    if (raceDefBonus > 0) {
      baseDef = (baseDef * (1 + raceDefBonus)).round();
    }

    final raceCritBonus =
        race?.getPassiveValue(PassiveType.criticalBonus) ?? 0.0;
    criRate += raceCritBonus;

    // ========================================================================
    // 클래스 패시브 적용 (Phase 5)
    // ========================================================================

    final classHpBonus =
        klass?.getPassiveValue(ClassPassiveType.hpBonus) ?? 0.0;
    if (classHpBonus > 0) {
      totalHpMax = (totalHpMax * (1 + classHpBonus)).round();
    }

    final classPhysBonus =
        klass?.getPassiveValue(ClassPassiveType.physicalDamageBonus) ?? 0.0;
    if (classPhysBonus > 0) {
      baseAtk = (baseAtk * (1 + classPhysBonus)).round();
    }

    final classDefBonus =
        klass?.getPassiveValue(ClassPassiveType.defenseBonus) ?? 0.0;
    if (classDefBonus > 0) {
      baseDef = (baseDef * (1 + classDefBonus)).round();
    }

    final classMagBonus =
        klass?.getPassiveValue(ClassPassiveType.magicDamageBonus) ?? 0.0;
    if (classMagBonus > 0) {
      baseMagAtk = (baseMagAtk * (1 + classMagBonus)).round();
    }

    final classEvasionBonus =
        klass?.getPassiveValue(ClassPassiveType.evasionBonus) ?? 0.0;
    evasion += classEvasionBonus;

    final classCritBonus =
        klass?.getPassiveValue(ClassPassiveType.criticalBonus) ?? 0.0;
    criRate += classCritBonus;

    // ========================================================================
    // 레벨 페널티 및 최종 클램핑 (Phase 12)
    // ========================================================================

    criRate *= levelPenalty;
    evasion *= levelPenalty;
    var finalBlockRate = blockRate * levelPenalty;
    var finalParryRate = parryRate * levelPenalty;

    criRate = criRate.clamp(0.05, _maxCriRate);
    evasion = evasion.clamp(0.0, _maxEvasion);
    finalBlockRate = finalBlockRate.clamp(0.0, _maxBlockRate);
    finalParryRate = finalParryRate.clamp(0.0, _maxParryRate);

    return CombatStats(
      str: effectiveStr,
      con: effectiveCon,
      dex: effectiveDex,
      intelligence: effectiveInt,
      wis: effectiveWis,
      cha: effectiveCha,
      atk: baseAtk,
      def: baseDef,
      magAtk: baseMagAtk,
      magDef: baseMagDef,
      criRate: criRate,
      criDamage: criDamage,
      evasion: evasion,
      accuracy: accuracy,
      blockRate: finalBlockRate,
      parryRate: finalParryRate,
      attackDelayMs: attackDelayMs,
      hpMax: totalHpMax,
      hpCurrent: stats.hp.clamp(0, totalHpMax),
      mpMax: totalMpMax,
      mpCurrent: stats.mp.clamp(0, totalMpMax),
    );
  }

  /// 테스트/디버그용 기본값
  factory CombatStats.empty() => const CombatStats(
    str: 10,
    con: 10,
    dex: 10,
    intelligence: 10,
    wis: 10,
    cha: 10,
    atk: 20,
    def: 10,
    magAtk: 20,
    magDef: 10,
    criRate: 0.05,
    criDamage: 1.5,
    evasion: 0.05,
    accuracy: 0.85,
    blockRate: 0.0,
    parryRate: 0.0,
    attackDelayMs: 1000,
    hpMax: 100,
    hpCurrent: 100,
    mpMax: 50,
    mpCurrent: 50,
  );
}
