import 'package:asciineverdie/data/potion_data.dart';
import 'package:asciineverdie/src/core/engine/potion_service.dart';
import 'package:asciineverdie/src/core/model/monster_grade.dart';
import 'package:asciineverdie/src/core/model/potion.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  const service = PotionService();

  // ============================================================================
  // 물약 사용 가능 여부 (canUsePotion)
  // ============================================================================

  group('canUsePotion', () {
    test('보유 물약이 있으면 사용 가능', () {
      final inventory = const PotionInventory().addPotion(
        PotionData.minorHealthPatch.id,
        3,
      );
      final (canUse, reason) = service.canUsePotion(
        PotionData.minorHealthPatch.id,
        inventory,
      );
      expect(canUse, isTrue);
      expect(reason, isNull);
    });

    test('존재하지 않는 물약 ID는 potionNotFound', () {
      final (canUse, reason) = service.canUsePotion(
        'nonexistent_potion',
        PotionInventory.empty,
      );
      expect(canUse, isFalse);
      expect(reason, PotionUseFailReason.potionNotFound);
    });

    test('보유 수량이 0이면 outOfStock', () {
      final (canUse, reason) = service.canUsePotion(
        PotionData.minorHealthPatch.id,
        PotionInventory.empty,
      );
      expect(canUse, isFalse);
      expect(reason, PotionUseFailReason.outOfStock);
    });
  });

  // ============================================================================
  // 물약 사용 (usePotion)
  // ============================================================================

  group('usePotion', () {
    test('HP 물약 사용 시 HP가 회복된다', () {
      final potion = PotionData.minorHealthPatch; // 고정 30, 비율 0%
      final inventory = PotionInventory.empty.addPotion(potion.id);

      final result = service.usePotion(
        potionId: potion.id,
        inventory: inventory,
        currentHp: 50,
        maxHp: 100,
        currentMp: 30,
        maxMp: 50,
      );

      expect(result.success, isTrue);
      expect(result.newHp, 80); // 50 + 30 = 80
      expect(result.healedAmount, 30);
      // MP는 변하지 않음
      expect(result.newMp, 30);
      // 인벤토리에서 수량 감소
      expect(result.newInventory!.hasPotion(potion.id), isFalse);
    });

    test('HP 물약은 maxHp를 초과하지 않는다', () {
      final potion = PotionData.minorHealthPatch;
      final inventory = PotionInventory.empty.addPotion(potion.id);

      final result = service.usePotion(
        potionId: potion.id,
        inventory: inventory,
        currentHp: 90,
        maxHp: 100,
        currentMp: 50,
        maxMp: 50,
      );

      expect(result.success, isTrue);
      expect(result.newHp, 100); // 90 + 30 = 120 → clamp → 100
      expect(result.healedAmount, 10); // 실제 회복량 = 100 - 90
    });

    test('MP 물약 사용 시 MP가 회복된다', () {
      final potion = PotionData.minorManaCache; // 고정 20, 비율 0%
      final inventory = PotionInventory.empty.addPotion(potion.id);

      final result = service.usePotion(
        potionId: potion.id,
        inventory: inventory,
        currentHp: 100,
        maxHp: 100,
        currentMp: 10,
        maxMp: 50,
      );

      expect(result.success, isTrue);
      expect(result.newMp, 30); // 10 + 20 = 30
      expect(result.healedAmount, 20);
      // HP는 변하지 않음
      expect(result.newHp, 100);
    });

    test('비율 회복(percent heal) 물약은 최대치 비례로 회복', () {
      final potion = PotionData.healthPatch; // 고정 50, 비율 10%
      final inventory = PotionInventory.empty.addPotion(potion.id);

      final result = service.usePotion(
        potionId: potion.id,
        inventory: inventory,
        currentHp: 100,
        maxHp: 1000,
        currentMp: 50,
        maxMp: 50,
      );

      // 고정 50 + (1000 * 0.10) = 50 + 100 = 150
      expect(result.success, isTrue);
      expect(result.newHp, 250); // 100 + 150
      expect(result.healedAmount, 150);
    });

    test('healingMultiplier 적용 시 회복량 증가', () {
      final potion = PotionData.minorHealthPatch; // 고정 30
      final inventory = PotionInventory.empty.addPotion(potion.id);

      final result = service.usePotion(
        potionId: potion.id,
        inventory: inventory,
        currentHp: 50,
        maxHp: 200,
        currentMp: 50,
        maxMp: 50,
        healingMultiplier: 1.5, // +50% 회복력 보너스(bonus)
      );

      // 30 * 1.5 = 45
      expect(result.success, isTrue);
      expect(result.newHp, 95); // 50 + 45
      expect(result.healedAmount, 45);
    });

    test('보유 물약이 없으면 실패', () {
      final result = service.usePotion(
        potionId: PotionData.minorHealthPatch.id,
        inventory: PotionInventory.empty,
        currentHp: 50,
        maxHp: 100,
        currentMp: 30,
        maxMp: 50,
      );

      expect(result.success, isFalse);
      expect(result.failReason, PotionUseFailReason.outOfStock);
    });
  });

  // ============================================================================
  // 물약 구매 (purchasePotion)
  // ============================================================================

  group('purchasePotion', () {
    test('골드가 충분하면 구매 성공', () {
      final potion = PotionData.minorHealthPatch; // 가격 25
      final result = service.purchasePotion(
        potionId: potion.id,
        inventory: PotionInventory.empty,
        gold: 100,
      );

      expect(result.success, isTrue);
      expect(result.totalCost, 25);
      expect(result.newGold, 75);
      expect(result.newInventory!.getQuantity(potion.id), 1);
    });

    test('복수 구매 시 총 비용 정확 계산', () {
      final potion = PotionData.minorHealthPatch; // 가격 25
      final result = service.purchasePotion(
        potionId: potion.id,
        inventory: PotionInventory.empty,
        gold: 1000,
        count: 5,
      );

      expect(result.success, isTrue);
      expect(result.totalCost, 125); // 25 * 5
      expect(result.newGold, 875);
      expect(result.newInventory!.getQuantity(potion.id), 5);
    });

    test('골드 부족 시 구매 실패', () {
      final potion = PotionData.ultraHealthPatch; // 가격 1200
      final result = service.purchasePotion(
        potionId: potion.id,
        inventory: PotionInventory.empty,
        gold: 100,
      );

      expect(result.success, isFalse);
      expect(result.failReason, PotionPurchaseFailReason.insufficientGold);
    });

    test('존재하지 않는 물약 ID는 구매 실패', () {
      final result = service.purchasePotion(
        potionId: 'invalid_potion',
        inventory: PotionInventory.empty,
        gold: 99999,
      );

      expect(result.success, isFalse);
      expect(result.failReason, PotionPurchaseFailReason.potionNotFound);
    });
  });

  // ============================================================================
  // canPurchasePotion - 구매 가능 여부 체크
  // ============================================================================

  group('canPurchasePotion', () {
    test('골드 충분 시 구매 가능', () {
      final (canBuy, reason) = service.canPurchasePotion(
        PotionData.minorHealthPatch.id,
        100,
      );
      expect(canBuy, isTrue);
      expect(reason, isNull);
    });

    test('골드 부족 시 insufficientGold', () {
      final (canBuy, reason) = service.canPurchasePotion(
        PotionData.ultraHealthPatch.id,
        10,
      );
      expect(canBuy, isFalse);
      expect(reason, PotionPurchaseFailReason.insufficientGold);
    });
  });

  // ============================================================================
  // 자동 구매 (autoPurchasePotions)
  // ============================================================================

  group('autoPurchasePotions', () {
    test('레벨에 맞는 티어(tier) 물약을 자동 구매', () {
      final result = service.autoPurchasePotions(
        playerLevel: 5, // 티어 1
        inventory: PotionInventory.empty,
        gold: 10000,
      );

      expect(result.success, isTrue);
      expect(result.quantity, greaterThan(0));
      expect(result.totalCost, greaterThan(0));
      // 사용 골드 비율은 20% 이내
      expect(result.totalCost, lessThanOrEqualTo(2000));
    });

    test('골드가 0이면 구매 실패', () {
      final result = service.autoPurchasePotions(
        playerLevel: 5,
        inventory: PotionInventory.empty,
        gold: 0,
      );

      expect(result.success, isFalse);
    });
  });

  // ============================================================================
  // 물약 드랍 (potion drop)
  // ============================================================================

  group('tryPotionDrop', () {
    test('roll이 드랍 확률(drop chance) 미만이면 물약 드랍', () {
      final (inventory, potion) = service.tryPotionDrop(
        playerLevel: 10,
        monsterLevel: 10,
        monsterGrade: MonsterGrade.normal,
        inventory: PotionInventory.empty,
        roll: 0, // 항상 드랍 성공
        typeRoll: 30, // HP 물약 (< 60)
      );

      expect(potion, isNotNull);
      expect(potion!.isHpPotion, isTrue);
      expect(inventory.hasPotion(potion.id), isTrue);
    });

    test('roll이 드랍 확률 이상이면 드랍 실패', () {
      final (inventory, potion) = service.tryPotionDrop(
        playerLevel: 1,
        monsterLevel: 1,
        monsterGrade: MonsterGrade.normal,
        inventory: PotionInventory.empty,
        roll: 99, // 거의 무조건 실패
        typeRoll: 30,
      );

      expect(potion, isNull);
      // 인벤토리 변화 없음
      expect(inventory.potions, isEmpty);
    });

    test('typeRoll >= 60이면 MP 물약 드랍', () {
      final (_, potion) = service.tryPotionDrop(
        playerLevel: 10,
        monsterLevel: 10,
        monsterGrade: MonsterGrade.normal,
        inventory: PotionInventory.empty,
        roll: 0,
        typeRoll: 70, // MP 물약 (>= 60)
      );

      expect(potion, isNotNull);
      expect(potion!.isMpPotion, isTrue);
    });

    test('보스(boss) 몬스터는 드랍 확률 +15%', () {
      // 일반 몬스터 Lv1 기본 드랍 확률 = 15%
      // 보스 보너스 +15% → 총 30%

      // roll=25: 일반은 실패(25 >= 15), 보스는 성공(25 < 30)
      final (_, normalPotion) = service.tryPotionDrop(
        playerLevel: 1,
        monsterLevel: 1,
        monsterGrade: MonsterGrade.normal,
        inventory: PotionInventory.empty,
        roll: 25,
        typeRoll: 30,
      );

      final (_, bossPotion) = service.tryPotionDrop(
        playerLevel: 1,
        monsterLevel: 1,
        monsterGrade: MonsterGrade.boss,
        inventory: PotionInventory.empty,
        roll: 25,
        typeRoll: 30,
      );

      expect(normalPotion, isNull);
      expect(bossPotion, isNotNull);
    });

    test('몬스터 레벨 > 플레이어 레벨이면 추가 확률 부여', () {
      // 기본 15% + 레벨 차이 10 * 1% = 25%
      // roll=20: 레벨 차이 없으면 실패(20 >= 15), 있으면 성공(20 < 25)
      final (_, withoutDiffPotion) = service.tryPotionDrop(
        playerLevel: 10,
        monsterLevel: 10,
        monsterGrade: MonsterGrade.normal,
        inventory: PotionInventory.empty,
        roll: 20,
        typeRoll: 30,
      );

      final (_, withDiffPotion) = service.tryPotionDrop(
        playerLevel: 10,
        monsterLevel: 20,
        monsterGrade: MonsterGrade.normal,
        inventory: PotionInventory.empty,
        roll: 20,
        typeRoll: 30,
      );

      // Lv10 기본 확률 = 15 + 10*0.5 = 20% → roll 20 >= 20 → 실패
      expect(withoutDiffPotion, isNull);
      // 레벨 차이 10 → 추가 10% → 30% → roll 20 < 30 → 성공
      expect(withDiffPotion, isNotNull);
    });

    test('기존 인벤토리에 물약 추가', () {
      final existingInventory = PotionInventory.empty.addPotion(
        PotionData.minorHealthPatch.id,
        5,
      );

      final (inventory, potion) = service.tryPotionDrop(
        playerLevel: 1,
        monsterLevel: 1,
        monsterGrade: MonsterGrade.normal,
        inventory: existingInventory,
        roll: 0, // 드랍 성공
        typeRoll: 30, // HP 물약
      );

      expect(potion, isNotNull);
      // 기존 5개 + 새로 1개
      final droppedId = potion!.id;
      if (droppedId == PotionData.minorHealthPatch.id) {
        expect(inventory.getQuantity(droppedId), 6);
      } else {
        // 다른 티어(tier) 물약이 드랍될 수 있음
        expect(inventory.getQuantity(droppedId), 1);
        expect(inventory.getQuantity(PotionData.minorHealthPatch.id), 5);
      }
    });
  });

  // ============================================================================
  // 인벤토리 관리 (inventory management)
  // ============================================================================

  group('addPotionDrop', () {
    test('물약 드랍 추가 시 수량 증가', () {
      final result = service.addPotionDrop(
        PotionInventory.empty,
        PotionData.minorHealthPatch.id,
        3,
      );
      expect(result.getQuantity(PotionData.minorHealthPatch.id), 3);
    });
  });

  // ============================================================================
  // 긴급 물약 선택 (emergency potion selection)
  // ============================================================================

  group('selectEmergencyHpPotion', () {
    test('HP 손실이 회복량 이상이면 물약 선택', () {
      final inventory = PotionInventory.empty.addPotion(
        PotionData.minorHealthPatch.id,
        5,
      );

      // maxHp=100, currentHp=50 → 손실 50 >= 회복량 30
      final potion = service.selectEmergencyHpPotion(
        currentHp: 50,
        maxHp: 100,
        inventory: inventory,
        playerLevel: 5,
      );

      expect(potion, isNotNull);
      expect(potion!.id, PotionData.minorHealthPatch.id);
    });

    test('HP가 만충(full)이면 null', () {
      final inventory = PotionInventory.empty.addPotion(
        PotionData.minorHealthPatch.id,
        5,
      );

      final potion = service.selectEmergencyHpPotion(
        currentHp: 100,
        maxHp: 100,
        inventory: inventory,
        playerLevel: 5,
      );

      expect(potion, isNull);
    });

    test('보유 물약이 없으면 null', () {
      final potion = service.selectEmergencyHpPotion(
        currentHp: 10,
        maxHp: 100,
        inventory: PotionInventory.empty,
        playerLevel: 5,
      );

      expect(potion, isNull);
    });
  });

  group('selectEmergencyMpPotion', () {
    test('MP 손실이 회복량 이상이면 물약 선택', () {
      final inventory = PotionInventory.empty.addPotion(
        PotionData.minorManaCache.id,
        5,
      );

      // maxMp=100, currentMp=50 → 손실 50 >= 회복량 20
      final potion = service.selectEmergencyMpPotion(
        currentMp: 50,
        maxMp: 100,
        inventory: inventory,
        playerLevel: 5,
      );

      expect(potion, isNotNull);
      expect(potion!.id, PotionData.minorManaCache.id);
    });

    test('MP가 만충(full)이면 null', () {
      final inventory = PotionInventory.empty.addPotion(
        PotionData.minorManaCache.id,
        5,
      );

      final potion = service.selectEmergencyMpPotion(
        currentMp: 50,
        maxMp: 50,
        inventory: inventory,
        playerLevel: 5,
      );

      expect(potion, isNull);
    });
  });
}
