package game_test

import (
	"math/rand/v2"
	"testing"

	"galaxy/util"

	"galaxy/game/internal/model/game"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
)

func TestCargoCapacity(t *testing.T) {
	test := func(cargoSize float64, expectCapacity float64) {
		ship := game.ShipType{
			Drive:    1,
			Armament: 1,
			Weapons:  1,
			Shields:  1,
			Cargo:    game.F(cargoSize),
		}
		sg := game.ShipGroup{
			Number: 1,
			Tech: map[game.Tech]game.Float{
				game.TechDrive:   game.F(1.5),
				game.TechWeapons: game.F(1.1),
				game.TechShields: game.F(2.0),
				game.TechCargo:   game.F(1.0),
			},
		}
		assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship))
	}
	test(1, 1.05)
	test(5, 6.25)
	test(10, 15)
	test(50, 175)
	test(100, 600)
}

func TestCarryingAndFullMass(t *testing.T) {
	Freighter := &game.ShipType{
		Name:     "Freighter",
		Drive:    8,
		Armament: 0,
		Weapons:  0,
		Shields:  2,
		Cargo:    10,
	}
	sg := &game.ShipGroup{
		Number: 1,
		Tech: map[game.Tech]game.Float{
			game.TechDrive:   game.F(1.0),
			game.TechWeapons: game.F(1.0),
			game.TechShields: game.F(1.0),
			game.TechCargo:   game.F(1.0),
		},
		Load: 0.0,
	}
	em := Freighter.EmptyMass()
	assert.Equal(t, 0.0, sg.CarryingMass())
	assert.Equal(t, em, sg.FullMass(Freighter))

	sg.Load = 10.0
	assert.Equal(t, 10.0, sg.CarryingMass())
	assert.Equal(t, em+10.0, sg.FullMass(Freighter))

	sg.SetTechLevel(game.TechCargo, 2.5)
	assert.Equal(t, 4.0, sg.CarryingMass())
	assert.Equal(t, em+4.0, sg.FullMass(Freighter))
}

func TestSpeed(t *testing.T) {
	Freighter := &game.ShipType{
		Name:     "Freighter",
		Drive:    8,
		Armament: 0,
		Weapons:  0,
		Shields:  2,
		Cargo:    10,
	}
	sg := &game.ShipGroup{
		Number: 1,
		Tech: map[game.Tech]game.Float{
			game.TechDrive:   game.F(1.0),
			game.TechWeapons: game.F(1.0),
			game.TechShields: game.F(1.0),
			game.TechCargo:   game.F(1.0),
		},
		Load: 0.0,
	}
	assert.Equal(t, 8.0, sg.Speed(Freighter))
	sg.Load = 5.0
	assert.Equal(t, 6.4, sg.Speed(Freighter))
	sg.SetTechLevel(game.TechDrive, 1.5)
	assert.Equal(t, 9.6, sg.Speed(Freighter))
	sg.Load = 10
	sg.SetTechLevel(game.TechCargo, 1.5)
	assert.Equal(t, 9.0, sg.Speed(Freighter))
}

func TestBombingPower(t *testing.T) {
	BattleStation := game.ShipType{
		Name:     "Battle_Station",
		Drive:    60.0,
		Armament: 3,
		Weapons:  30.0,
		Shields:  100.0,
		Cargo:    0.0,
	}
	sg := game.ShipGroup{
		Number: 1,
		Tech: map[game.Tech]game.Float{
			game.TechDrive:   game.F(1.0),
			game.TechWeapons: game.F(1.0),
			game.TechShields: game.F(1.0),
			game.TechCargo:   game.F(1.0),
		},
	}
	assert.Equal(t, 139.295, util.Fixed3(sg.BombingPower(&BattleStation)))
	sg.Number = 2
	assert.Equal(t, 278.590, util.Fixed3(sg.BombingPower(&BattleStation)))
}

func TestDriveEffective(t *testing.T) {
	tc := []struct {
		driveShipType        game.Float
		driveTech            game.Float
		expectDriveEffective game.Float
	}{
		{1, 1, 1},
		{1, 2, 2},
		{2, 1, 2},
		{0, 1, 0},
		{0, 1.5, 0},
		{0, 10, 0},
		{1.5, 1.5, 2.25},
	}
	for i := range tc {
		someShip := game.ShipType{
			Drive:    tc[i].driveShipType,
			Armament: rand.UintN(30) + 1,
			Weapons:  game.F(rand.Float64()*30 + 1),
			Shields:  game.F(rand.Float64()*100 + 1),
			Cargo:    game.F(rand.Float64()*20 + 1),
		}
		sg := game.ShipGroup{
			Number: rand.UintN(4) + 1,
			Tech: map[game.Tech]game.Float{
				game.TechDrive:   tc[i].driveTech,
				game.TechWeapons: game.F(rand.Float64()*5 + 1),
				game.TechShields: game.F(rand.Float64()*5 + 1),
				game.TechCargo:   game.F(rand.Float64()*5 + 1),
			},
		}
		assert.Equal(t, tc[i].expectDriveEffective.F(), sg.DriveEffective(&someShip))
	}
}

func TestShipGroupEqual(t *testing.T) {
	fleetId := uuid.New()
	someUUID := uuid.New()
	mat := game.CargoMaterial
	cap := game.CargoCapital
	left := &game.ShipGroup{
		ID:     uuid.New(),
		Number: 1,

		OwnerID:   uuid.New(),
		TypeID:    uuid.New(),
		FleetID:   &fleetId,
		CargoType: &mat,
		Load:      123.45,
		Tech: map[game.Tech]game.Float{
			game.TechDrive:   1.0,
			game.TechWeapons: 1.0,
			game.TechShields: 1.0,
			game.TechCargo:   1.0,
		},
	}

	// essential properties
	right := *left
	assert.True(t, left.Equal(right))

	left.OwnerID = someUUID
	assert.False(t, left.Equal(right))

	right = *left
	left.TypeID = someUUID
	assert.False(t, left.Equal(right))

	right = *left
	left.FleetID = &someUUID
	assert.False(t, left.Equal(right))

	right = *left
	left.FleetID = nil
	assert.False(t, left.Equal(right))

	right = *left
	coord := game.Float(1)
	left.StateInSpace = &game.InSpace{
		Origin: 1,
		X:      &coord,
		Y:      &coord,
	}
	assert.False(t, left.Equal(right))

	right = *left
	left.CargoType = &cap
	assert.False(t, left.Equal(right))

	right = *left
	left.CargoType = nil
	assert.False(t, left.Equal(right))

	right = *left
	left.Load = 45.123
	assert.False(t, left.Equal(right))

	right = *left
	left.SetTechLevel(game.TechDrive, 1.1)
	assert.Equal(t, 1.1, left.TechLevel(game.TechDrive).F())
	assert.False(t, left.Equal(right))

	right = *left
	left.SetTechLevel(game.TechWeapons, 1.1)
	assert.Equal(t, 1.1, left.TechLevel(game.TechWeapons).F())
	assert.False(t, left.Equal(right))

	right = *left
	left.SetTechLevel(game.TechShields, 1.1)
	assert.Equal(t, 1.1, left.TechLevel(game.TechShields).F())
	assert.False(t, left.Equal(right))

	right = *left
	left.SetTechLevel(game.TechCargo, 1.1)
	assert.Equal(t, 1.1, left.TechLevel(game.TechCargo).F())
	assert.False(t, left.Equal(right))

	// non-essential properties
	right = *left

	left.ID = uuid.New()
	assert.True(t, left.Equal(right))

	// dirty hack to equalize loads
	left.Number = 5
	left.Load = game.F(float64(right.Load) / float64(right.Number) * float64(left.Number))
	assert.True(t, left.Equal(right))
}
