package main

import (
	"math/rand"
	"testing"
)

func TestApplyDamageAbsorbedByShieldFirst(t *testing.T) {
	shield, hull := 10, 50
	applyDamage(&shield, &hull, 6)
	if shield != 4 || hull != 50 {
		t.Errorf("урон должен был полностью уйти в щит: shield=%d hull=%d", shield, hull)
	}
}

func TestApplyDamageOverflowsToHull(t *testing.T) {
	shield, hull := 5, 50
	applyDamage(&shield, &hull, 12)
	if shield != 0 || hull != 43 {
		t.Errorf("избыток урона должен был перейти в корпус: shield=%d hull=%d", shield, hull)
	}
}

func TestApplyDamageHullNeverNegative(t *testing.T) {
	shield, hull := 0, 5
	applyDamage(&shield, &hull, 100)
	if hull != 0 {
		t.Errorf("корпус не должен уходить в минус, получено %d", hull)
	}
}

func TestAttackCanDefeatEnemy(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 5, 5, 100, 100, 50, 50, 100, true, WayfarerWeaponLaser, 0, rnd)
	c.Attack(WayfarerShieldFore, rnd)
	if c.Outcome != CombatWon {
		t.Fatalf("огромный урон игрока должен был сразу уничтожить слабого врага, получен исход %v (EnemyHull=%d)",
			c.Outcome, c.EnemyHull)
	}
	if c.LastEnemyDamage != 0 {
		t.Errorf("уничтоженный враг не должен успевать ответным ударом")
	}
}

func TestAttackEnemySurvivesAndRetaliates(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 5, true, WayfarerWeaponLaser, 0, rnd)
	beforeHull := c.PlayerHull + c.TotalShield()
	c.Attack(WayfarerShieldFore, rnd)
	if c.Outcome != CombatOngoing {
		t.Fatalf("бой должен был продолжиться, получен исход %v", c.Outcome)
	}
	afterHull := c.PlayerHull + c.TotalShield()
	if afterHull >= beforeHull {
		t.Errorf("выживший враг должен был нанести ответный урон")
	}
}

func TestAttackCanDefeatPlayer(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 500, 1, 100, 0, 50, 1, true, WayfarerWeaponLaser, 0, rnd)
	c.Attack(WayfarerShieldFore, rnd)
	if c.Outcome != CombatLost {
		t.Fatalf("слабый игрок против огромного урона должен был проиграть, получен исход %v (PlayerHull=%d)",
			c.Outcome, c.PlayerHull)
	}
}

func TestFleeEventuallySucceedsOrFails(t *testing.T) {
	successSeen, failSeen := false, false
	for seed := int64(0); seed < 200 && !(successSeen && failSeen); seed++ {
		rnd := rand.New(rand.NewSource(seed))
		c := NewWayfarerCombat("enemy.pirate", 1000, 5, 1000, 1000, 100, 100, 5, true, WayfarerWeaponLaser, 0, rnd)
		if c.Flee(rnd) {
			successSeen = true
		} else {
			failSeen = true
		}
	}
	if !successSeen || !failSeen {
		t.Errorf("ожидались оба исхода бегства среди множества попыток: успех=%v неудача=%v", successSeen, failSeen)
	}
}

func TestFailedFleeStillTakesDamage(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 20, 1000, 1000, 0, 0, 5, true, WayfarerWeaponLaser, 0, rnd)
	for i := 0; i < 50 && c.Outcome == CombatOngoing; i++ {
		fled := c.Flee(rnd)
		if !fled {
			if c.LastEnemyDamage <= 0 {
				t.Errorf("неудачное бегство должно было нанести урон игроку")
			}
			return
		}
	}
}

func TestNegotiateOnlyAvailableWhenNegotiable(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 1000, 1000, 100, 100, 5, false, WayfarerWeaponLaser, 0, rnd)
	before := *c
	ok := c.Negotiate(rnd)
	if ok {
		t.Errorf("переговоры не должны быть доступны, если Negotiable=false")
	}
	if c.Round != before.Round {
		t.Errorf("недоступная попытка переговоров не должна тратить раунд")
	}
}

func TestNegotiateEventuallySucceedsOrFails(t *testing.T) {
	successSeen, failSeen := false, false
	for seed := int64(0); seed < 200 && !(successSeen && failSeen); seed++ {
		rnd := rand.New(rand.NewSource(seed))
		c := NewWayfarerCombat("enemy.pirate", 1000, 5, 1000, 1000, 100, 100, 5, true, WayfarerWeaponLaser, 0, rnd)
		if c.Negotiate(rnd) {
			successSeen = true
		} else {
			failSeen = true
		}
	}
	if !successSeen || !failSeen {
		t.Errorf("ожидались оба исхода переговоров среди множества попыток: успех=%v неудача=%v", successSeen, failSeen)
	}
}

// --- Ракеты: урон слабее по щиту, сильнее по голому корпусу --------

func TestApplyWeaponDamageMissileWeakerVsShield(t *testing.T) {
	shieldLaser, hullLaser := 20, 50
	applyWeaponDamage(&shieldLaser, &hullLaser, 10, WayfarerWeaponLaser)

	shieldMissile, hullMissile := 20, 50
	applyWeaponDamage(&shieldMissile, &hullMissile, 10, WayfarerWeaponMissileRegular)

	if shieldMissile <= shieldLaser {
		t.Errorf("ракета должна была снять со щита меньше, чем лазер: щит после лазера=%d, после ракеты=%d", shieldLaser, shieldMissile)
	}
	if hullMissile != 50 || hullLaser != 50 {
		t.Errorf("при достаточном щите урон не должен доходить до корпуса ни у лазера, ни у ракеты")
	}
}

func TestApplyWeaponDamageMissileStrongerVsBareHull(t *testing.T) {
	shieldLaser, hullLaser := 0, 50
	applyDamage(&shieldLaser, &hullLaser, 10)

	shieldMissile, hullMissile := 0, 50
	applyWeaponDamage(&shieldMissile, &hullMissile, 10, WayfarerWeaponMissileRegular)

	if (50 - hullMissile) <= (50 - hullLaser) {
		t.Errorf("ракета по голому корпусу должна наносить больше урона, чем лазер: лазер снял %d, ракета %d", 50-hullLaser, 50-hullMissile)
	}
}

func TestApplyWeaponDamageHomingSameFormulaAsRegular(t *testing.T) {
	shieldA, hullA := 5, 50
	applyWeaponDamage(&shieldA, &hullA, 10, WayfarerWeaponMissileRegular)

	shieldB, hullB := 5, 50
	applyWeaponDamage(&shieldB, &hullB, 10, WayfarerWeaponMissileHoming)

	if shieldA != shieldB || hullA != hullB {
		t.Errorf("обычная и самонаводящаяся ракета должны наносить одинаковый урон по формуле, получено (%d,%d) и (%d,%d)", shieldA, hullA, shieldB, hullB)
	}
}

// --- AttackWithMissile ------------------------------------------------

func TestAttackWithMissileConsumesAmmoAndDamagesEnemy(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	ship := &WayfarerShip{RegularMissiles: 2}

	if err := c.AttackWithMissile(ship, WayfarerWeaponMissileRegular, WayfarerShieldFore, rnd); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if ship.RegularMissiles != 1 {
		t.Errorf("ожидалось списание одной ракеты, осталось %d", ship.RegularMissiles)
	}
	if c.EnemyHull >= 1000 {
		t.Errorf("враг должен был получить урон")
	}
}

func TestAttackWithMissileFailsWithoutAmmo(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	ship := &WayfarerShip{RegularMissiles: 0}

	if err := c.AttackWithMissile(ship, WayfarerWeaponMissileRegular, WayfarerShieldFore, rnd); err != errWayfarerNoMissiles {
		t.Errorf("ожидалась ошибка нехватки ракет, получено: %v", err)
	}
}

func TestAttackWithMissileUsesCorrectAmmoType(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	ship := &WayfarerShip{HomingMissiles: 1}

	if err := c.AttackWithMissile(ship, WayfarerWeaponMissileHoming, WayfarerShieldFore, rnd); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if ship.HomingMissiles != 0 {
		t.Errorf("ожидалось списание самонаводящейся ракеты, осталось %d", ship.HomingMissiles)
	}
}

func TestAttackWithMissileTargetsChosenFacing(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 40, rnd)
	ship := &WayfarerShip{RegularMissiles: 1}
	rightBefore := c.EnemyShieldRight

	if err := c.AttackWithMissile(ship, WayfarerWeaponMissileRegular, WayfarerShieldRight, rnd); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if c.EnemyShieldRight >= rightBefore {
		t.Errorf("щит врага именно с правого борта должен был уменьшиться, было %d, стало %d", rightBefore, c.EnemyShieldRight)
	}
}

// --- Dodge / UseDecoy --------------------------------------------------

func TestDodgeOnlyWorksAgainstRegularMissile(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	cLaser := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	if cLaser.Dodge(rnd) {
		t.Errorf("уклонение не должно приносить успех против лазера — оно вообще неприменимо")
	}
	if cLaser.Round != 0 {
		t.Errorf("неприменимое уклонение не должно тратить раунд")
	}

	cHoming := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponMissileHoming, 0, rnd)
	if cHoming.Dodge(rnd) {
		t.Errorf("уклонение не должно работать против самонаводящейся ракеты")
	}
	if cHoming.Round != 0 {
		t.Errorf("неприменимое против самонаводящейся ракеты уклонение не должно тратить раунд")
	}
}

func TestDodgeEventuallySucceedsAndFailsAgainstRegularMissile(t *testing.T) {
	successSeen, failSeen := false, false
	for seed := int64(0); seed < 200 && !(successSeen && failSeen); seed++ {
		rnd := rand.New(rand.NewSource(seed))
		c := NewWayfarerCombat("enemy.pirate", 1000, 5, 1000, 1000, 100, 100, 5, true, WayfarerWeaponMissileRegular, 0, rnd)
		if c.Dodge(rnd) {
			successSeen = true
		} else {
			failSeen = true
		}
	}
	if !successSeen || !failSeen {
		t.Errorf("ожидались оба исхода уклонения: успех=%v неудача=%v", successSeen, failSeen)
	}
}

func TestUseDecoyOnlyWorksAgainstHomingMissileWithDecoysAvailable(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponMissileHoming, 0, rnd)
	shipNoDecoys := &WayfarerShip{Decoys: 0}
	if c.UseDecoy(shipNoDecoys) {
		t.Errorf("ловушка не должна срабатывать без запаса ловушек")
	}

	cLaser := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	shipWithDecoys := &WayfarerShip{Decoys: 3}
	if cLaser.UseDecoy(shipWithDecoys) {
		t.Errorf("ловушка не должна применяться против лазера — она нужна только против самонаводящихся ракет")
	}
	if shipWithDecoys.Decoys != 3 {
		t.Errorf("неприменимая ловушка не должна расходоваться")
	}
}

func TestUseDecoySucceedsAndConsumesOne(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponMissileHoming, 0, rnd)
	ship := &WayfarerShip{Decoys: 2}

	if !c.UseDecoy(ship) {
		t.Fatalf("ловушка должна была сработать против самонаводящейся ракеты при наличии запаса")
	}
	if ship.Decoys != 1 {
		t.Errorf("ожидалось списание одной ловушки, осталось %d", ship.Decoys)
	}
	if c.LastEnemyDamage != 0 {
		t.Errorf("успешное применение ловушки должно было полностью нейтрализовать урон в этом раунде")
	}
}

// --- DistributeShields --------------------------------------------------

func TestDistributeShieldsRejectsExceedingTotal(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	roundBefore := c.Round
	if err := c.DistributeShields(30, 30, 30, 30, rnd); err != errWayfarerInvalidShieldDistribution {
		t.Errorf("ожидалась ошибка превышения общего запаса, получено: %v", err)
	}
	if c.Round != roundBefore {
		t.Errorf("отклонённое распределение не должно тратить раунд")
	}
}

func TestDistributeShieldsAppliesNewSplit(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 50, 50, 20, true, WayfarerWeaponLaser, 0, rnd)
	if err := c.DistributeShields(50, 0, 0, 0, rnd); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if c.PlayerShieldFore != 50 || c.PlayerShieldAft != 0 || c.PlayerShieldLeft != 0 || c.PlayerShieldRight != 0 {
		t.Errorf("новое распределение не применилось: нос=%d хвост=%d лево=%d право=%d",
			c.PlayerShieldFore, c.PlayerShieldAft, c.PlayerShieldLeft, c.PlayerShieldRight)
	}
}

func TestDistributeShieldsSpendsRoundAndEnemyStrikes(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 500, 1000, 1000, 30, 30, 20, true, WayfarerWeaponLaser, 0, rnd)
	roundBefore := c.Round
	c.DistributeShields(10, 10, 5, 5, rnd)
	if c.Round != roundBefore+1 {
		t.Errorf("подтверждённое распределение должно было потратить раунд")
	}
	if c.LastEnemyDamage <= 0 {
		t.Errorf("после распределения враг должен был ответить атакой")
	}
}

// --- Щиты врага (симметричные щитам игрока) -----------------------------

func TestEnemyShieldsSplitEvenlyAtStart(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 0, 0, 20, true, WayfarerWeaponLaser, 40, rnd)
	if c.TotalEnemyShield() != 40 {
		t.Errorf("ожидался суммарный щит врага 40, получено %d", c.TotalEnemyShield())
	}
	if c.EnemyShieldAft != 10 || c.EnemyShieldLeft != 10 || c.EnemyShieldRight != 10 {
		t.Errorf("щит врага должен был разложиться поровну по 4 направлениям, получено нос=%d хвост=%d лево=%d право=%d",
			c.EnemyShieldFore, c.EnemyShieldAft, c.EnemyShieldLeft, c.EnemyShieldRight)
	}
}

func TestAttackDepletesOnlyChosenEnemyFacing(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 100, 100, 0, 0, 20, true, WayfarerWeaponLaser, 40, rnd)
	foreBefore, aftBefore, leftBefore, rightBefore := c.EnemyShieldFore, c.EnemyShieldAft, c.EnemyShieldLeft, c.EnemyShieldRight

	c.Attack(WayfarerShieldLeft, rnd)

	if c.EnemyShieldLeft >= leftBefore {
		t.Errorf("щит врага слева должен был уменьшиться, было %d, стало %d", leftBefore, c.EnemyShieldLeft)
	}
	if c.EnemyShieldFore != foreBefore || c.EnemyShieldAft != aftBefore || c.EnemyShieldRight != rightBefore {
		t.Errorf("атака в левый борт не должна была задеть другие направления щита врага")
	}
}

// --- Заход врага (NextApproach) сужает возможные направления удара ------

func TestApproachCandidatesMatchSpecifiedMapping(t *testing.T) {
	tests := []struct {
		approach WayfarerApproach
		want     []WayfarerShieldFacing
	}{
		{WayfarerApproachFront, []WayfarerShieldFacing{WayfarerShieldFore}},
		{WayfarerApproachBehind, []WayfarerShieldFacing{WayfarerShieldAft}},
		{WayfarerApproachLeft, []WayfarerShieldFacing{WayfarerShieldLeft, WayfarerShieldAft}},
		{WayfarerApproachRight, []WayfarerShieldFacing{WayfarerShieldRight, WayfarerShieldAft}},
		{WayfarerApproachVertical, []WayfarerShieldFacing{WayfarerShieldFore, WayfarerShieldAft}},
	}
	for _, tt := range tests {
		got := wayfarerApproachCandidates[tt.approach]
		if len(got) != len(tt.want) {
			t.Fatalf("заход %v: ожидалось %d направлений, получено %d (%v)", tt.approach, len(tt.want), len(got), got)
		}
		for i := range tt.want {
			if got[i] != tt.want[i] {
				t.Errorf("заход %v: ожидался набор %v, получено %v", tt.approach, tt.want, got)
			}
		}
	}
}

func TestEnemyStrikeOnlyHitsFacingsAllowedByApproach(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 1000, 1000, 100, 100, 5, true, WayfarerWeaponLaser, 0, rnd)

	for i := 0; i < 300 && c.Outcome == CombatOngoing; i++ {
		c.NextApproach = WayfarerApproachLeft // фиксируем заход слева на весь тест
		c.enemyStrikes(rnd)
		if c.LastHitFacing != WayfarerShieldLeft && c.LastHitFacing != WayfarerShieldAft {
			t.Fatalf("при заходе слева удар должен приходиться только в левый борт или хвост, получено %v", c.LastHitFacing)
		}
	}
}

func TestFrontApproachAlwaysHitsFore(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 1000, 5, 1000, 1000, 100, 100, 5, true, WayfarerWeaponLaser, 0, rnd)
	for i := 0; i < 50 && c.Outcome == CombatOngoing; i++ {
		c.NextApproach = WayfarerApproachFront
		c.enemyStrikes(rnd)
		if c.LastHitFacing != WayfarerShieldFore {
			t.Fatalf("при заходе строго спереди удар должен идти точно в нос, получено %v", c.LastHitFacing)
		}
	}
}

// --- Покупка ракет/ловушек на корабле ------------------------------------

func TestBuyMissileRespectsCapacity(t *testing.T) {
	s := &WayfarerShip{Credits: 100000, MissileCapacity: 1}
	if err := s.BuyMissile(WayfarerWeaponMissileRegular, 100); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := s.BuyMissile(WayfarerWeaponMissileHoming, 100); err != errWayfarerNoMissileSlots {
		t.Errorf("ожидалась ошибка нехватки слотов, получено: %v", err)
	}
}

func TestBuyMissileInsufficientFunds(t *testing.T) {
	s := &WayfarerShip{Credits: 10, MissileCapacity: 2}
	if err := s.BuyMissile(WayfarerWeaponMissileRegular, 100); err != errWayfarerInsufficientFunds {
		t.Errorf("ожидалась ошибка нехватки средств, получено: %v", err)
	}
}

func TestBuyDecoyIncrementsCount(t *testing.T) {
	s := &WayfarerShip{Credits: 1000}
	if err := s.BuyDecoy(80); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if s.Decoys != 1 {
		t.Errorf("ожидалась 1 ловушка, получено %d", s.Decoys)
	}
}

func TestActionsAfterCombatOverAreNoOps(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	c := NewWayfarerCombat("enemy.pirate", 5, 5, 100, 100, 50, 50, 100, true, WayfarerWeaponLaser, 0, rnd)
	c.Attack(WayfarerShieldFore, rnd)
	if c.Outcome != CombatWon {
		t.Fatalf("бой должен был завершиться победой для настройки теста")
	}
	roundAfter := c.Round
	c.Attack(WayfarerShieldFore, rnd)
	c.Flee(rnd)
	c.Negotiate(rnd)
	if c.Round != roundAfter {
		t.Errorf("действия после завершения боя не должны ничего менять")
	}
}
