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)
	c.Attack(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)
	beforeHull := c.PlayerHull + c.PlayerShield
	c.Attack(rnd)
	if c.Outcome != CombatOngoing {
		t.Fatalf("бой должен был продолжиться, получен исход %v", c.Outcome)
	}
	afterHull := c.PlayerHull + c.PlayerShield
	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)
	c.Attack(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)
		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)
	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)
	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)
		if c.Negotiate(rnd) {
			successSeen = true
		} else {
			failSeen = true
		}
	}
	if !successSeen || !failSeen {
		t.Errorf("ожидались оба исхода переговоров среди множества попыток: успех=%v неудача=%v", successSeen, failSeen)
	}
}

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