import 'package:asciineverdie/src/core/engine/death_handler.dart';
import 'package:asciineverdie/src/core/model/equipment_container.dart';
import 'package:asciineverdie/src/core/model/equipment_item.dart';
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
import 'package:flutter_test/flutter_test.dart';

import '../../helpers/mock_factories.dart';

void main() {
  const handler = DeathHandler();

  /// 테스트용 장비 생성 헬퍼
  EquipmentItem _makeItem(String name, EquipmentSlot slot) {
    return EquipmentItem(
      name: name,
      slot: slot,
      level: 5,
      weight: 3,
      stats: const ItemStats(def: 10),
      rarity: ItemRarity.rare,
    );
  }

  /// 모든 슬롯에 장비가 장착된 Equipment 생성
  Equipment _fullyEquipped() {
    return Equipment(
      items: [
        EquipmentItem.defaultWeapon(), // 0: 무기(weapon)
        _makeItem('Iron Shield', EquipmentSlot.shield),
        _makeItem('Iron Helm', EquipmentSlot.helm),
        _makeItem('Iron Hauberk', EquipmentSlot.hauberk),
        _makeItem('Iron Brassairts', EquipmentSlot.brassairts),
        _makeItem('Iron Vambraces', EquipmentSlot.vambraces),
        _makeItem('Iron Gauntlets', EquipmentSlot.gauntlets),
        _makeItem('Iron Gambeson', EquipmentSlot.gambeson),
        _makeItem('Iron Cuisses', EquipmentSlot.cuisses),
        _makeItem('Iron Greaves', EquipmentSlot.greaves),
        _makeItem('Iron Sollerets', EquipmentSlot.sollerets),
      ],
      bestIndex: 0,
    );
  }

  /// 테스트용 GameState 생성
  ///
  /// [seed]: 결정론적 랜덤 시드(deterministic random seed)
  /// [level]: 캐릭터 레벨
  /// [isBossFight]: 보스전(boss fight) 여부
  /// [equipment]: 커스텀 장비
  GameState _createState({
    int seed = 42,
    int level = 1,
    bool isBossFight = false,
    Equipment? equipment,
  }) {
    final state = GameState(
      rng: DeterministicRandom(seed),
      traits: Traits.empty().copyWith(level: level),
      equipment: equipment ?? _fullyEquipped(),
      progress: ProgressState.empty().copyWith(
        currentCombat: MockFactories.createCombat(),
        finalBossState: isBossFight
            ? FinalBossState.fighting
            : FinalBossState.notSpawned,
      ),
      inventory: const Inventory(gold: 5000, items: []),
    );
    return state;
  }

  group('DeathHandler', () {
    group('deathInfo 생성', () {
      test('사망 시 deathInfo가 올바르게 생성된다', () {
        // 준비(arrange): Lv10 캐릭터 - 100% 장비 손실 확률
        final state = _createState(level: 10);

        // 실행(act)
        final result = handler.processPlayerDeath(
          state,
          killerName: 'Dark Goblin',
          cause: DeathCause.monster,
        );

        // 검증(assert): deathInfo 필드 확인
        expect(result.deathInfo, isNotNull);
        expect(result.deathInfo!.cause, DeathCause.monster);
        expect(result.deathInfo!.killerName, 'Dark Goblin');
        expect(result.deathInfo!.goldAtDeath, 5000);
        expect(result.deathInfo!.levelAtDeath, 10);
        expect(result.isDead, isTrue);
      });

      test('selfDamage 원인으로 사망 시 cause가 올바르다', () {
        final state = _createState(level: 1);

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Self',
          cause: DeathCause.selfDamage,
        );

        expect(result.deathInfo!.cause, DeathCause.selfDamage);
      });
    });

    group('보스전(boss fight) 사망', () {
      test('보스전 사망 시 장비가 보호된다 (lostCount == 0)', () {
        final equipment = _fullyEquipped();
        final state = _createState(
          level: 10,
          isBossFight: true,
          equipment: equipment,
        );

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Final Boss',
          cause: DeathCause.monster,
        );

        // 검증: 장비 손실 없음
        expect(result.deathInfo!.lostEquipmentCount, 0);
        expect(result.deathInfo!.lostItemName, isNull);
        expect(result.deathInfo!.lostItem, isNull);

        // 검증: 모든 장비가 그대로 유지
        for (var i = 0; i < Equipment.slotCount; i++) {
          expect(
            result.equipment.getItemByIndex(i).name,
            equipment.getItemByIndex(i).name,
          );
        }
      });

      test('보스전 사망 시 bossLevelingEndTime이 설정된다 (5분)', () {
        final state = _createState(isBossFight: true);
        final before = DateTime.now().millisecondsSinceEpoch;

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Final Boss',
          cause: DeathCause.monster,
        );

        final after = DateTime.now().millisecondsSinceEpoch;
        final endTime = result.progress.bossLevelingEndTime;

        // 검증: 5분(300,000ms) 후 시간이 설정됨
        expect(endTime, isNotNull);
        // before + 5분 <= endTime <= after + 5분 (타이밍 허용)
        const fiveMinMs = 5 * 60 * 1000;
        expect(endTime!, greaterThanOrEqualTo(before + fiveMinMs));
        expect(endTime, lessThanOrEqualTo(after + fiveMinMs));
      });

      test('일반 사망 시 bossLevelingEndTime은 null이다', () {
        final state = _createState(level: 1);

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Goblin',
          cause: DeathCause.monster,
        );

        expect(result.progress.bossLevelingEndTime, isNull);
      });
    });

    group('장비 손실(equipment loss) 확률', () {
      test('Lv1: 20% 확률 - roll < 20이면 장비 손실', () {
        // Lv1 공식: 20 + (1-1)*80/9 = 20%
        // 시드를 탐색하여 roll < 20인 경우를 찾아야 함
        // DeterministicRandom(seed).nextInt(100)의 결과가 20 미만인 시드 사용
        int? lossySeed;
        for (var s = 0; s < 1000; s++) {
          final rng = DeterministicRandom(s);
          final roll = rng.nextInt(100);
          if (roll < 20) {
            lossySeed = s;
            break;
          }
        }
        expect(lossySeed, isNotNull, reason: '장비 손실 시드를 찾을 수 없음');

        final state = _createState(seed: lossySeed!, level: 1);
        final result = handler.processPlayerDeath(
          state,
          killerName: 'Goblin',
          cause: DeathCause.monster,
        );

        expect(result.deathInfo!.lostEquipmentCount, 1);
      });

      test('Lv1: 20% 확률 - roll >= 20이면 장비 보호', () {
        // roll >= 20인 시드 찾기
        int? safeSeed;
        for (var s = 0; s < 1000; s++) {
          final rng = DeterministicRandom(s);
          final roll = rng.nextInt(100);
          if (roll >= 20) {
            safeSeed = s;
            break;
          }
        }
        expect(safeSeed, isNotNull, reason: '장비 보호 시드를 찾을 수 없음');

        final state = _createState(seed: safeSeed!, level: 1);
        final result = handler.processPlayerDeath(
          state,
          killerName: 'Goblin',
          cause: DeathCause.monster,
        );

        expect(result.deathInfo!.lostEquipmentCount, 0);
        expect(result.deathInfo!.lostItemName, isNull);
      });

      test('Lv10+: 100% 확률로 장비 손실', () {
        // Lv10 이상은 항상 100% 손실
        final state = _createState(seed: 42, level: 10);

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Dragon',
          cause: DeathCause.monster,
        );

        expect(result.deathInfo!.lostEquipmentCount, 1);
        expect(result.deathInfo!.lostItemName, isNotNull);
        expect(result.deathInfo!.lostItemSlot, isNotNull);
        expect(result.deathInfo!.lostItem, isNotNull);
      });

      test('Lv5: 중간 확률 (~55%) 공식 검증', () {
        // Lv5 공식: 20 + (5-1)*80/9 = 20 + 35 = 55
        // (정수 나눗셈: (4*80)~/9 = 320~/9 = 35)
        final level = 5;
        final expected = 20 + ((level - 1) * 80 ~/ 9);
        expect(expected, 55);
      });
    });

    group('무기(weapon) 슬롯 보호', () {
      test('무기(슬롯 0)는 손실 대상에서 제외된다', () {
        // Lv10(100% 손실)으로 여러 시드를 반복 테스트
        // 무기는 절대 사라지지 않아야 함
        for (var s = 0; s < 50; s++) {
          final state = _createState(seed: s, level: 10);
          final result = handler.processPlayerDeath(
            state,
            killerName: 'Monster',
            cause: DeathCause.monster,
          );

          // 무기(weapon)는 항상 유지
          expect(
            result.equipment.weaponItem.isNotEmpty,
            isTrue,
            reason: 'seed=$s에서 무기가 사라짐',
          );

          // 손실된 아이템이 있다면 weapon 슬롯이 아니어야 함
          if (result.deathInfo!.lostItemSlot != null) {
            expect(
              result.deathInfo!.lostItemSlot,
              isNot(EquipmentSlot.weapon),
              reason: 'seed=$s에서 무기 슬롯이 손실됨',
            );
          }
        }
      });
    });

    group('빈 장비(empty equipment)', () {
      test('무기 외 모든 장비가 비어있으면 손실 없음', () {
        // 무기만 있고 나머지는 빈 장비
        final emptyEquipment = Equipment.empty();
        // Equipment.empty()는 기본 무기(Keyboard)만 있음
        final state = _createState(
          seed: 42,
          level: 10, // 100% 손실 확률
          equipment: emptyEquipment,
        );

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Monster',
          cause: DeathCause.monster,
        );

        // 검증: 빈 슬롯만 있으므로 손실 불가
        expect(result.deathInfo!.lostEquipmentCount, 0);
        expect(result.deathInfo!.lostItemName, isNull);
      });
    });

    group('deathCount 증가', () {
      test('사망 시 deathCount가 1 증가한다', () {
        final state = _createState();
        expect(state.progress.deathCount, 0);

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Goblin',
          cause: DeathCause.monster,
        );

        expect(result.progress.deathCount, 1);
      });

      test('연속 사망 시 deathCount가 누적된다', () {
        // 첫 번째 사망 (deathCount: 0 -> 1)
        var state = _createState(seed: 100);
        state = handler.processPlayerDeath(
          state,
          killerName: 'Goblin',
          cause: DeathCause.monster,
        );
        expect(state.progress.deathCount, 1);

        // 부활 후 전투 재개 시뮬레이션 (deathInfo 초기화)
        state = state.copyWith(clearDeathInfo: true);

        // 두 번째 사망 (deathCount: 1 -> 2)
        state = handler.processPlayerDeath(
          state,
          killerName: 'Dragon',
          cause: DeathCause.monster,
        );
        expect(state.progress.deathCount, 2);
      });
    });

    group('currentCombat 초기화', () {
      test('사망 후 currentCombat이 null로 초기화되어야 한다', () {
        // 전투 중(combat active) 상태에서 사망
        final state = _createState();
        expect(state.progress.currentCombat, isNotNull);

        final result = handler.processPlayerDeath(
          state,
          killerName: 'Monster',
          cause: DeathCause.monster,
        );

        // clearCurrentCombat 파라미터로 전투 상태 초기화
        expect(result.progress.currentCombat, isNull);
      });
    });
  });
}
