import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
import 'package:asciineverdie/src/core/model/combat_stats.dart';
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('CombatCalculator', () {
    group('playerAttackMonster', () {
      test('데미지 = (ATK * variation) - (DEF * 0.4)', () {
        // 고정 시드로 예측 가능한 결과
        final rng = DeterministicRandom(42);
        final calculator = CombatCalculator(rng: rng);

        final player = CombatStats.empty().copyWith(
          atk: 100,
          accuracy: 1.0, // 100% 명중
          criRate: 0.0, // 크리티컬 없음
          criDamage: 1.5,
        );

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 10,
          def: 50, // DEF 50 → 50 * 0.4 = 20 감소
          magDef: 50,
          hpMax: 100,
          hpCurrent: 100,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 0.0, // 회피 없음
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 100,
        );

        final result = calculator.playerAttackMonster(
          attacker: player,
          defender: monster,
        );

        // ATK 100 * (0.8~1.2) - DEF 50 * 0.4 = 80~120 - 20 = 60~100
        expect(result.result.damage, greaterThanOrEqualTo(1));
        expect(result.result.isHit, isTrue);
        expect(result.result.isEvaded, isFalse);
        expect(result.updatedDefender.hpCurrent, lessThan(monster.hpCurrent));
      });

      test('크리티컬 발동 시 데미지 배율 적용', () {
        // 크리티컬이 항상 발동하도록 criRate = 1.0
        final rng = DeterministicRandom(123);
        final calculator = CombatCalculator(rng: rng);

        final player = CombatStats.empty().copyWith(
          atk: 100,
          accuracy: 1.0, // 100% 명중
          criRate: 1.0, // 100% 크리티컬
          criDamage: 2.0, // 2배 데미지
        );

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 10,
          def: 0, // DEF 0
          magDef: 0,
          hpMax: 500,
          hpCurrent: 500,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 0.0, // 회피 없음
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 100,
        );

        final result = calculator.playerAttackMonster(
          attacker: player,
          defender: monster,
        );

        // 크리티컬 시 데미지가 2배 적용
        expect(result.result.isCritical, isTrue);
        // ATK 100 * (0.8~1.2) * 2.0 = 160~240
        expect(result.result.damage, greaterThanOrEqualTo(160));
      });

      test('회피 발동 시 0 데미지', () {
        final rng = DeterministicRandom(42);
        final calculator = CombatCalculator(rng: rng);

        final player = CombatStats.empty().copyWith(
          atk: 100,
          accuracy: 0.0, // 0% 명중 → 항상 회피
          criRate: 0.0,
          criDamage: 1.5,
        );

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 10,
          def: 0,
          magDef: 0,
          hpMax: 100,
          hpCurrent: 100,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 1.0, // 100% 회피
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 100,
        );

        final result = calculator.playerAttackMonster(
          attacker: player,
          defender: monster,
        );

        // 회피 시 데미지 0
        expect(result.result.damage, equals(0));
        expect(result.result.isHit, isFalse);
        expect(result.result.isEvaded, isTrue);
        expect(result.updatedDefender.hpCurrent, equals(monster.hpCurrent));
      });
    });

    group('monsterAttackPlayer', () {
      test('블록 발동 시 70% 감소', () {
        // 블록이 발동하는 시드 찾기
        // blockRate = 1.0으로 항상 블록
        final rng = DeterministicRandom(99);
        final calculator = CombatCalculator(rng: rng);

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 100,
          def: 0,
          magDef: 0,
          hpMax: 100,
          hpCurrent: 100,
          criRate: 0.0, // 크리티컬 없음
          criDamage: 1.5,
          evasion: 0.0,
          accuracy: 1.0, // 100% 명중
          attackDelayMs: 1000,
          expReward: 100,
        );

        final player = CombatStats.empty().copyWith(
          hpMax: 500,
          hpCurrent: 500,
          def: 0,
          evasion: 0.0,
          blockRate: 1.0, // 100% 블록
          parryRate: 0.0,
        );

        final result = calculator.monsterAttackPlayer(
          attacker: monster,
          defender: player,
        );

        // 블록 시 데미지 30% (70% 감소)
        // ATK 100 * (0.8~1.2) * 0.3 = 24~36
        expect(result.result.isBlocked, isTrue);
        expect(result.result.damage, lessThanOrEqualTo(40));
      });

      test('패리 발동 시 50% 감소', () {
        final rng = DeterministicRandom(77);
        final calculator = CombatCalculator(rng: rng);

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 100,
          def: 0,
          magDef: 0,
          hpMax: 100,
          hpCurrent: 100,
          criRate: 0.0,
          criDamage: 1.5,
          evasion: 0.0,
          accuracy: 1.0,
          attackDelayMs: 1000,
          expReward: 100,
        );

        final player = CombatStats.empty().copyWith(
          hpMax: 500,
          hpCurrent: 500,
          def: 0,
          evasion: 0.0,
          blockRate: 0.0, // 블록 없음
          parryRate: 1.0, // 100% 패리
        );

        final result = calculator.monsterAttackPlayer(
          attacker: monster,
          defender: player,
        );

        // 패리 시 데미지 50%
        // ATK 100 * (0.8~1.2) * 0.5 = 40~60
        expect(result.result.isParried, isTrue);
        expect(result.result.damage, lessThanOrEqualTo(65));
      });
    });

    group('estimateCombatDurationMs', () {
      test('범위 2000~30000ms 내 반환', () {
        final rng = DeterministicRandom(42);
        final calculator = CombatCalculator(rng: rng);

        final player = CombatStats.empty().copyWith(
          atk: 50,
          accuracy: 0.9,
          attackDelayMs: 1000,
        );

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 10,
          def: 10,
          magDef: 10,
          hpMax: 100,
          hpCurrent: 100,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 0.0,
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 100,
        );

        final duration = calculator.estimateCombatDurationMs(
          player: player,
          monster: monster,
        );

        expect(duration, greaterThanOrEqualTo(2000));
        expect(duration, lessThanOrEqualTo(30000));
      });

      test('고레벨 몬스터는 더 긴 전투 시간', () {
        final rng = DeterministicRandom(42);
        final calculator = CombatCalculator(rng: rng);

        final player = CombatStats.empty().copyWith(
          atk: 50,
          accuracy: 0.9,
          attackDelayMs: 1000,
        );

        final weakMonster = MonsterCombatStats(
          name: 'Weak Monster',
          level: 1,
          atk: 10,
          def: 5,
          magDef: 5,
          hpMax: 50,
          hpCurrent: 50,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 0.0,
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 50,
        );

        final strongMonster = MonsterCombatStats(
          name: 'Strong Monster',
          level: 10,
          atk: 50,
          def: 20,
          magDef: 20,
          hpMax: 500,
          hpCurrent: 500,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 0.0,
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 200,
        );

        final weakDuration = calculator.estimateCombatDurationMs(
          player: player,
          monster: weakMonster,
        );

        final strongDuration = calculator.estimateCombatDurationMs(
          player: player,
          monster: strongMonster,
        );

        // 강한 몬스터가 더 긴 전투 시간
        expect(strongDuration, greaterThan(weakDuration));
      });
    });

    group('evaluateDifficulty', () {
      test('범위 0.0~1.0 내 반환', () {
        final rng = DeterministicRandom(42);
        final calculator = CombatCalculator(rng: rng);

        final player = CombatStats.empty().copyWith(
          hpMax: 100,
          hpCurrent: 100,
          atk: 50,
          def: 20,
          accuracy: 0.9,
          attackDelayMs: 1000,
        );

        final monster = MonsterCombatStats(
          name: 'Test Monster',
          level: 1,
          atk: 30,
          def: 10,
          magDef: 10,
          hpMax: 80,
          hpCurrent: 80,
          criRate: 0.05,
          criDamage: 1.5,
          evasion: 0.0,
          accuracy: 0.8,
          attackDelayMs: 1000,
          expReward: 100,
        );

        final difficulty = calculator.evaluateDifficulty(
          player: player,
          monster: monster,
        );

        expect(difficulty, greaterThanOrEqualTo(0.0));
        expect(difficulty, lessThanOrEqualTo(1.0));
      });
    });
  });
}
