package main

import "testing"

func TestNewWayfarerShipInitialState(t *testing.T) {
	start := WayfarerSystemID{Galaxy: 0, Index: 0}
	s := NewWayfarerShip(start)
	if s.Credits != 1000 {
		t.Errorf("ожидался стартовый капитал 1000, получено %v", s.Credits)
	}
	if s.CurrentSystem != start {
		t.Errorf("корабль должен был начать в стартовой системе")
	}
	if !s.Visited[start] {
		t.Errorf("стартовая система должна была отметиться как посещённая")
	}
	if s.CargoUsed() != 0 || s.CargoFree() != s.CargoCapacity {
		t.Errorf("трюм новой партии должен быть пуст")
	}
}

func TestBuyAndSellCommodity(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	before := s.Credits
	if err := s.BuyCommodity(0, 5, 10); err != nil {
		t.Fatalf("неожиданная ошибка покупки: %v", err)
	}
	if s.Cargo[0] != 5 {
		t.Errorf("ожидалось 5 единиц товара в трюме, получено %d", s.Cargo[0])
	}
	if s.Credits != before-50 {
		t.Errorf("ожидалось списание 50 кредитов, получено %v (было %v)", s.Credits, before)
	}

	if err := s.SellCommodity(0, 5, 8); err != nil {
		t.Fatalf("неожиданная ошибка продажи: %v", err)
	}
	if s.Cargo[0] != 0 {
		t.Errorf("трюм должен был опустеть после продажи всего")
	}
	if s.Credits != before-50+40 {
		t.Errorf("ожидалось начисление 40 кредитов от продажи, получено %v", s.Credits)
	}
}

func TestBuyCommodityInsufficientFunds(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	s.Credits = 5
	if err := s.BuyCommodity(0, 10, 10); err != errWayfarerInsufficientFunds {
		t.Errorf("ожидалась ошибка нехватки средств, получено: %v", err)
	}
}

func TestBuyCommodityCargoFull(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	s.Credits = 1_000_000
	if err := s.BuyCommodity(0, s.CargoCapacity+1, 1); err != errWayfarerCargoFull {
		t.Errorf("ожидалась ошибка переполнения трюма, получено: %v", err)
	}
}

func TestSellCommodityNotEnoughInCargo(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	if err := s.SellCommodity(0, 1, 10); err != errWayfarerNoCargo {
		t.Errorf("ожидалась ошибка нехватки товара в трюме, получено: %v", err)
	}
}

func TestBuyCommodityTracksAverageCostBasis(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	s.Credits = 1_000_000

	if err := s.BuyCommodity(0, 10, 20); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if s.CargoCostBasis[0] != 20 {
		t.Errorf("после первой покупки средняя цена должна быть 20, получено %v", s.CargoCostBasis[0])
	}

	// вторая партия по другой цене — средняя должна пересчитаться
	// как взвешенное среднее: (10*20 + 10*40) / 20 = 30
	if err := s.BuyCommodity(0, 10, 40); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if s.CargoCostBasis[0] != 30 {
		t.Errorf("ожидалась средняя цена 30 после второй закупки, получено %v", s.CargoCostBasis[0])
	}
}

func TestSellCommodityResetsCostBasisWhenCargoEmpty(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	s.Credits = 1_000_000
	s.BuyCommodity(0, 5, 20)

	if err := s.SellCommodity(0, 3, 25); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if s.CargoCostBasis[0] != 20 {
		t.Errorf("частичная продажа не должна менять среднюю цену оставшегося груза, получено %v", s.CargoCostBasis[0])
	}

	if err := s.SellCommodity(0, 2, 25); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if s.CargoCostBasis[0] != 0 {
		t.Errorf("после полной продажи средняя цена должна была сброситься в 0, получено %v", s.CargoCostBasis[0])
	}
}

func TestRefuelClampsAtCapacity(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	s.Fuel = s.FuelCapacity - 1
	s.Credits = 1_000_000
	if err := s.Refuel(100, 1); err != nil {
		t.Fatalf("неожиданная ошибка дозаправки: %v", err)
	}
	if s.Fuel != s.FuelCapacity {
		t.Errorf("топливо должно было ограничиться ёмкостью бака, получено %v", s.Fuel)
	}
}

func TestRepairClampsAtMaxHull(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	s.Hull = s.MaxHull - 1
	s.Credits = 1_000_000
	if err := s.Repair(100, 1); err != nil {
		t.Fatalf("неожиданная ошибка ремонта: %v", err)
	}
	if s.Hull != s.MaxHull {
		t.Errorf("прочность должна была ограничиться максимумом, получено %d", s.Hull)
	}
}

func TestJumpToConsumesFuelByDistance(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{Galaxy: 0, Index: 0})
	target := WayfarerSystemID{Galaxy: 0, Index: 1}
	before := s.Fuel
	if err := s.JumpTo(target); err != nil {
		t.Fatalf("неожиданная ошибка прыжка: %v", err)
	}
	if s.Fuel >= before {
		t.Errorf("топливо должно было уменьшиться после прыжка")
	}
	if s.CurrentSystem != target {
		t.Errorf("корабль должен был оказаться в целевой системе")
	}
	if !s.Visited[target] {
		t.Errorf("целевая система должна была отметиться как посещённая")
	}
}

func TestJumpToSameSystemFails(t *testing.T) {
	start := WayfarerSystemID{Galaxy: 0, Index: 0}
	s := NewWayfarerShip(start)
	if err := s.JumpTo(start); err != errWayfarerSameSystem {
		t.Errorf("ожидалась ошибка прыжка в ту же систему, получено: %v", err)
	}
}

func TestJumpToOutOfRangeFails(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{Galaxy: 0, Index: 0})
	s.Fuel = 0.0001
	far := WayfarerSystemID{Galaxy: 0, Index: 1}
	err := s.JumpTo(far)
	if err != errWayfarerOutOfRange {
		far = WayfarerSystemID{Galaxy: 0, Index: 50}
		s2 := NewWayfarerShip(WayfarerSystemID{Galaxy: 0, Index: 0})
		s2.Fuel = 0.0001
		if err2 := s2.JumpTo(far); err2 != errWayfarerOutOfRange {
			t.Fatalf("ожидалась ошибка нехватки топлива, получено: %v (и %v)", err, err2)
		}
	}
}

func TestPilotRatingIncreasesWithKills(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	initial := s.PilotRating()
	s.Kills = 500
	if s.PilotRating() == initial {
		t.Errorf("рейтинг должен был вырасти после 500 побед")
	}
}

func TestNetWorthIncludesCargoValue(t *testing.T) {
	s := NewWayfarerShip(WayfarerSystemID{})
	baseline := s.NetWorth()
	s.Cargo[0] = 10
	if s.NetWorth() <= baseline {
		t.Errorf("состояние должно было вырасти с добавлением груза в трюм")
	}
}
