package repo_test

import (
	"path/filepath"
	"testing"
	"time"

	"galaxy/model/order"

	"galaxy/game/internal/repo"
	"galaxy/game/internal/repo/fs"

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

func TestSaveOrder(t *testing.T) {
	root := t.ArtifactDir()
	s, err := fs.NewFileStorage(root)
	assert.NoError(t, err)
	id := uuid.New()
	gameID := uuid.New()
	now := time.Now().UTC().UnixMilli()
	o := &order.UserGamesOrder{
		GameID:    gameID,
		UpdatedAt: now,
		Commands: []order.DecodableCommand{
			&order.CommandRaceVote{
				CommandMeta: order.CommandMeta{
					CmdType: order.CommandTypeRaceVote,
					CmdID:   uuid.New().String(),
				},
				Acceptor: "Race_acc",
			},
			&order.CommandShipClassCreate{
				CommandMeta: order.CommandMeta{
					CmdType: order.CommandTypeShipClassCreate,
					CmdID:   uuid.New().String(),
				},
				Name:     "Fighter",
				Drive:    20.5,
				Armament: 5,
				Weapons:  20,
				Shields:  15.5,
				Cargo:    0,
			},
			&order.CommandShipGroupMerge{
				CommandMeta: order.CommandMeta{
					CmdType: order.CommandTypeShipGroupMerge,
					CmdID:   uuid.New().String(),
				},
			},
			&order.CommandShipClassCreate{
				CommandMeta: order.CommandMeta{
					CmdType: order.CommandTypeShipClassCreate,
					CmdID:   uuid.New().String(),
				},
				Name:     "Freighter",
				Drive:    30.33,
				Armament: 1,
				Weapons:  1,
				Shields:  10.1,
				Cargo:    0,
			},
			&order.CommandRaceQuit{
				CommandMeta: order.CommandMeta{
					CmdType: order.CommandTypeRaceQuit,
					CmdID:   uuid.New().String(),
				},
			},
		},
	}
	var turn uint = 2

	for i := range o.Commands {
		if v, ok := order.AsCommand[*order.CommandRaceVote](o.Commands[i]); ok {
			m := &v.CommandMeta
			m.Result(0)
		} else if v, ok := order.AsCommand[*order.CommandRaceQuit](o.Commands[i]); ok {
			v.Result(10)
		} else if v, ok := order.AsCommand[*order.CommandShipClassCreate](o.Commands[i]); ok {
			m := &v.CommandMeta
			m.Result(33)
		} else if v, ok := order.AsCommand[*order.CommandShipGroupMerge](o.Commands[i]); ok {
			v.Result(0)
		}
	}

	assert.NoError(t, repo.SaveOrder_T(s, turn, id, o))
	assert.FileExists(t, filepath.Join(root, repo.OrderDir(turn, id)))

	LoadOrderTest(t, s, root, turn, id, o)
}

func LoadOrderTest(t *testing.T, s repo.Storage, root string, turn uint, id uuid.UUID, expected *order.UserGamesOrder) {
	o, ok, err := repo.LoadOrder_T(s, turn, id)
	assert.NoError(t, err)
	assert.True(t, ok)
	assert.Len(t, o.Commands, 5)
	assert.Equal(t, expected.GameID, o.GameID)
	assert.Equal(t, expected.UpdatedAt, o.UpdatedAt)
	assert.ElementsMatch(t, expected.Commands, o.Commands)

	CommandResultTest(t, o)
}

func TestSaveOrderEmptyRoundTrip(t *testing.T) {
	// An empty order is a legal player intent (the user removed
	// every command from the draft). The repo round-trips it as an
	// `(*UserGamesOrder, true, nil)` triple with `Commands` empty
	// so the front-end can distinguish "no order yet" (ok=false)
	// from "order exists but is empty" (ok=true).
	root := t.ArtifactDir()
	s, err := fs.NewFileStorage(root)
	assert.NoError(t, err)
	id := uuid.New()
	gameID := uuid.New()
	now := time.Now().UTC().UnixMilli()
	o := &order.UserGamesOrder{
		GameID:    gameID,
		UpdatedAt: now,
	}
	var turn uint = 3

	assert.NoError(t, repo.SaveOrder_T(s, turn, id, o))
	assert.FileExists(t, filepath.Join(root, repo.OrderDir(turn, id)))

	loaded, ok, err := repo.LoadOrder_T(s, turn, id)
	assert.NoError(t, err)
	assert.True(t, ok, "empty order must surface as ok=true so callers can tell it apart from a missing one")
	assert.NotNil(t, loaded)
	assert.Equal(t, gameID, loaded.GameID)
	assert.Equal(t, now, loaded.UpdatedAt)
	assert.Empty(t, loaded.Commands)
}

func TestLoadOrderMissing(t *testing.T) {
	// A turn that has never had a PUT must come back as
	// `(nil, false, nil)` — the engine's "no stored order" path.
	root := t.ArtifactDir()
	s, err := fs.NewFileStorage(root)
	assert.NoError(t, err)
	id := uuid.New()

	loaded, ok, err := repo.LoadOrder_T(s, 7, id)
	assert.NoError(t, err)
	assert.False(t, ok)
	assert.Nil(t, loaded)
}

func CommandResultTest(t *testing.T, o *order.UserGamesOrder) {
	assert.NotEmpty(t, o.Commands)
	for i := range o.Commands {
		if v, ok := order.AsCommand[*order.CommandRaceVote](o.Commands[i]); ok {
			assert.NotNil(t, v.CmdApplied)
			assert.True(t, *v.CmdApplied)
			assert.Equal(t, 0, *v.CmdErrCode)
		} else if v, ok := order.AsCommand[*order.CommandRaceQuit](o.Commands[i]); ok {
			assert.NotNil(t, v.CmdApplied)
			assert.False(t, *v.CmdApplied)
			assert.Equal(t, 10, *v.CmdErrCode)
		} else if v, ok := order.AsCommand[*order.CommandShipClassCreate](o.Commands[i]); ok {
			assert.NotNil(t, v.CmdApplied)
			assert.False(t, *v.CmdApplied)
			assert.Equal(t, 33, *v.CmdErrCode)
		} else if v, ok := order.AsCommand[*order.CommandShipGroupMerge](o.Commands[i]); ok {
			assert.NotNil(t, v.CmdApplied)
			assert.True(t, *v.CmdApplied)
			assert.Equal(t, 0, *v.CmdErrCode)
		}
	}
}
