package calc_test

import (
	"testing"

	source "galaxy/calc"
	bridge "galaxy/core/calc"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// shipFixture is the input set passed to every parity check. Values
// are picked to exercise both the typical mid-tech ship and the
// invalid weapons/armament pairing path of EmptyMass /
// WeaponsBlockMass.
type shipFixture struct {
	name      string
	drive     float64
	armament  uint
	weapons   float64
	shields   float64
	cargo     float64
	driveTech float64
	cargoTech float64
}

func fixtures() []shipFixture {
	return []shipFixture{
		{
			name:      "all_zero",
			drive:     0, armament: 0, weapons: 0, shields: 0, cargo: 0,
			driveTech: 1, cargoTech: 1,
		},
		{
			name:      "typical_mid_tech",
			drive:     8, armament: 2, weapons: 5, shields: 3, cargo: 4,
			driveTech: 1.5, cargoTech: 1.2,
		},
		{
			name:      "heavy_armoured",
			drive:     3, armament: 5, weapons: 12, shields: 20, cargo: 1,
			driveTech: 0.8, cargoTech: 0.5,
		},
		{
			name:      "invalid_weapons_no_armament",
			drive:     5, armament: 0, weapons: 4, shields: 1, cargo: 2,
			driveTech: 1, cargoTech: 1,
		},
		{
			name:      "invalid_armament_no_weapons",
			drive:     5, armament: 3, weapons: 0, shields: 1, cargo: 2,
			driveTech: 1, cargoTech: 1,
		},
	}
}

func TestDriveEffectiveParity(t *testing.T) {
	t.Parallel()
	for _, f := range fixtures() {
		t.Run(f.name, func(t *testing.T) {
			t.Parallel()
			want := source.DriveEffective(f.drive, f.driveTech)
			got := bridge.DriveEffective(f.drive, f.driveTech)
			assert.Equal(t, want, got)
		})
	}
}

func TestEmptyMassParity(t *testing.T) {
	t.Parallel()
	for _, f := range fixtures() {
		t.Run(f.name, func(t *testing.T) {
			t.Parallel()
			wantMass, wantOk := source.EmptyMass(f.drive, f.weapons, f.armament, f.shields, f.cargo)
			gotMass, gotOk := bridge.EmptyMass(f.drive, f.weapons, f.armament, f.shields, f.cargo)
			assert.Equal(t, wantOk, gotOk)
			assert.Equal(t, wantMass, gotMass)
		})
	}
}

func TestWeaponsBlockMassParity(t *testing.T) {
	t.Parallel()
	for _, f := range fixtures() {
		t.Run(f.name, func(t *testing.T) {
			t.Parallel()
			wantMass, wantOk := source.WeaponsBlockMass(f.weapons, f.armament)
			gotMass, gotOk := bridge.WeaponsBlockMass(f.weapons, f.armament)
			assert.Equal(t, wantOk, gotOk)
			assert.Equal(t, wantMass, gotMass)
		})
	}
}

func TestFullMassParity(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name      string
		emptyMass float64
		carrying  float64
	}{
		{"zero", 0, 0},
		{"empty_only", 25, 0},
		{"loaded", 25, 12.5},
		{"negative_carrying_clamped_by_caller", 25, -3},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()
			want := source.FullMass(c.emptyMass, c.carrying)
			got := bridge.FullMass(c.emptyMass, c.carrying)
			assert.Equal(t, want, got)
		})
	}
}

func TestSpeedParity(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name           string
		driveEffective float64
		fullMass       float64
	}{
		{"zero_mass_returns_zero", 12, 0},
		{"negative_mass_returns_zero", 12, -1},
		{"typical", 12, 30},
		{"fast_light_ship", 50, 5},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()
			want := source.Speed(c.driveEffective, c.fullMass)
			got := bridge.Speed(c.driveEffective, c.fullMass)
			assert.Equal(t, want, got)
		})
	}
}

func TestCargoCapacityParity(t *testing.T) {
	t.Parallel()
	for _, f := range fixtures() {
		t.Run(f.name, func(t *testing.T) {
			t.Parallel()
			want := source.CargoCapacity(f.cargo, f.cargoTech)
			got := bridge.CargoCapacity(f.cargo, f.cargoTech)
			assert.Equal(t, want, got)
		})
	}
}

func TestCarryingMassParity(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name      string
		load      float64
		cargoTech float64
	}{
		{"zero_load", 0, 1},
		{"negative_load_returns_zero", -5, 1},
		{"typical_high_tech", 24, 2},
		{"low_tech_amplifies", 24, 0.5},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()
			want := source.CarryingMass(c.load, c.cargoTech)
			got := bridge.CarryingMass(c.load, c.cargoTech)
			assert.Equal(t, want, got)
		})
	}
}

func TestBlockUpgradeCostParity(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name        string
		blockMass   float64
		currentTech float64
		targetTech  float64
	}{
		{"zero_block_mass", 0, 1, 2},
		{"target_equal_to_current", 5, 2, 2},
		{"target_below_current", 5, 2, 1},
		{"doubling_tech_on_mass_5", 5, 1, 2},
		{"partial_step_2_to_2_5", 5, 2, 2.5},
		{"high_tech_to_higher_tech", 12, 4, 6},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()
			want := source.BlockUpgradeCost(c.blockMass, c.currentTech, c.targetTech)
			got := bridge.BlockUpgradeCost(c.blockMass, c.currentTech, c.targetTech)
			assert.Equal(t, want, got)
		})
	}
}

// TestDesignerPreviewComposition exercises the exact composition the
// ship-class designer performs: empty mass, full-load mass via
// CarryingMass(CargoCapacity), max speed at empty, and range at full
// load. Catches regressions if a future bridge tweak silently changes
// the composition shape.
func TestDesignerPreviewComposition(t *testing.T) {
	t.Parallel()
	const (
		drive     = 8.0
		armament  = uint(2)
		weapons   = 5.0
		shields   = 3.0
		cargo     = 4.0
		driveTech = 1.5
		cargoTech = 1.2
	)
	emptyMass, ok := bridge.EmptyMass(drive, weapons, armament, shields, cargo)
	require.True(t, ok)
	cargoCap := bridge.CargoCapacity(cargo, cargoTech)
	carryAtFull := bridge.CarryingMass(cargoCap, cargoTech)
	fullLoadMass := bridge.FullMass(emptyMass, carryAtFull)
	driveEff := bridge.DriveEffective(drive, driveTech)
	maxSpeed := bridge.Speed(driveEff, emptyMass)
	rangePerTurn := bridge.Speed(driveEff, fullLoadMass)

	wantEmpty, _ := source.EmptyMass(drive, weapons, armament, shields, cargo)
	wantCap := source.CargoCapacity(cargo, cargoTech)
	wantCarry := source.CarryingMass(wantCap, cargoTech)
	wantFull := source.FullMass(wantEmpty, wantCarry)
	wantDE := source.DriveEffective(drive, driveTech)
	wantMaxSpeed := source.Speed(wantDE, wantEmpty)
	wantRange := source.Speed(wantDE, wantFull)

	assert.Equal(t, wantEmpty, emptyMass)
	assert.Equal(t, wantCap, cargoCap)
	assert.Equal(t, wantCarry, carryAtFull)
	assert.Equal(t, wantFull, fullLoadMass)
	assert.Equal(t, wantMaxSpeed, maxSpeed)
	assert.Equal(t, wantRange, rangePerTurn)
}
