package order

import (
	"encoding/json"

	"github.com/google/uuid"
)

// MessageTypeUserGamesCommand is the authenticated gateway message type
// used to send a batch of in-game commands to the engine through
// `POST /api/v1/user/games/{game_id}/commands`. The signed payload is
// a FlatBuffers `order.UserGamesCommand`.
const MessageTypeUserGamesCommand = "user.games.command"

// MessageTypeUserGamesOrder is the authenticated gateway message type
// used to validate / store a batch of in-game orders through
// `POST /api/v1/user/games/{game_id}/orders`. The signed payload is a
// FlatBuffers `order.UserGamesOrder`.
const MessageTypeUserGamesOrder = "user.games.order"

// MessageTypeUserGamesOrderGet is the authenticated gateway message
// type used to read back the player's stored order for a given turn
// through `GET /api/v1/user/games/{game_id}/orders?turn=N`. The
// signed payload is a FlatBuffers `order.UserGamesOrderGet`.
const MessageTypeUserGamesOrderGet = "user.games.order.get"

// UserGamesCommand is the typed payload of MessageTypeUserGamesCommand.
// `GameID` selects the running engine container; `Commands` is the
// player command batch executed atomically by the engine. The `Actor`
// field present in the engine's JSON shape is rebuilt by backend from
// the runtime player mapping — clients never carry it.
type UserGamesCommand struct {
	// GameID identifies the running game for this batch.
	GameID uuid.UUID `json:"game_id"`

	// Commands is the player command batch.
	Commands []DecodableCommand `json:"cmd"`
}

// UserGamesOrder is the typed payload of MessageTypeUserGamesOrder.
// Mirrors `UserGamesCommand` plus an `UpdatedAt` field that lets the
// engine reject stale order submissions.
type UserGamesOrder struct {
	// GameID identifies the running game for this batch.
	GameID uuid.UUID `json:"game_id"`

	// UpdatedAt is the client-side timestamp used for stale-order
	// detection on the engine side.
	UpdatedAt int64 `json:"updatedAt"`

	// Commands is the player order batch.
	Commands []DecodableCommand `json:"cmd"`
}

func (o UserGamesOrder) MarshalBinary() (data []byte, err error) {
	return json.Marshal(&o)
}

func (o *UserGamesOrder) UnmarshalBinary(data []byte) error {
	return json.Unmarshal(data, o)
}

// UserGamesOrderGet is the typed payload of
// MessageTypeUserGamesOrderGet. `Turn` is mandatory and must be
// non-negative; the caller pulls it from the lobby record at game
// boot. Backend rebinds the player from the runtime player mapping
// before forwarding to the engine.
type UserGamesOrderGet struct {
	// GameID identifies the running game whose order is being
	// read back.
	GameID uuid.UUID `json:"game_id"`

	// Turn selects the turn the stored order belongs to. Negative
	// values are invalid.
	Turn int `json:"turn"`
}

func AsCommand[E DecodableCommand](c DecodableCommand) (result E, ok bool) {
	if v, ok := c.(E); ok {
		return v, true
	}
	return
}

type CommandType string

const (
	CommandTypeRaceQuit           CommandType = "raceQuit"
	CommandTypeRaceVote           CommandType = "raceVote"
	CommandTypeRaceRelation       CommandType = "raceRelation"
	CommandTypeShipClassCreate    CommandType = "shipClassCreate"
	CommandTypeShipClassMerge     CommandType = "shipClassMerge"
	CommandTypeShipClassRemove    CommandType = "shipClassRemove"
	CommandTypeShipGroupBreak     CommandType = "shipGroupBreak"
	CommandTypeShipGroupLoad      CommandType = "shipGroupLoad"
	CommandTypeShipGroupUnload    CommandType = "shipGroupUnload"
	CommandTypeShipGroupSend      CommandType = "shipGroupSend"
	CommandTypeShipGroupUpgrade   CommandType = "shipGroupUpgrade"
	CommandTypeShipGroupMerge     CommandType = "shipGroupMerge"
	CommandTypeShipGroupDismantle CommandType = "shipGroupDismantle"
	CommandTypeShipGroupTransfer  CommandType = "shipGroupTransfer"
	CommandTypeShipGroupJoinFleet CommandType = "shipGroupJoinFleet"
	CommandTypeFleetMerge         CommandType = "fleetMerge"
	CommandTypeFleetSend          CommandType = "fleetSend"
	CommandTypeScienceCreate      CommandType = "scienceCreate"
	CommandTypeScienceRemove      CommandType = "scienceRemove"
	CommandTypePlanetRename       CommandType = "planetRename"
	CommandTypePlanetProduce      CommandType = "planetProduce"
	CommandTypePlanetRouteSet     CommandType = "planetRouteSet"
	CommandTypePlanetRouteRemove  CommandType = "planetRouteRemove"
)

func (ct CommandType) String() string {
	return string(ct)
}

type DecodableCommand interface {
	CommandID() string
	CommandType() CommandType
}

type CommandMeta struct {
	CmdType    CommandType `json:"@type" binding:"notblank"`
	CmdID      string      `json:"cmdId" binding:"required,uuid_rfc4122"`
	CmdApplied *bool       `json:"cmdApplied,omitempty"`
	CmdErrCode *int        `json:"cmdErrorCode,omitempty"`
}

func (cm CommandMeta) CommandType() CommandType {
	return cm.CmdType
}

func (cm CommandMeta) CommandID() string {
	return cm.CmdID
}

func (cm *CommandMeta) Result(errCode int) {
	cm.CmdErrCode = &errCode
	cm.CmdApplied = new(bool(errCode == 0))
}

type CommandRaceQuit struct {
	CommandMeta
}

type CommandRaceVote struct {
	CommandMeta
	Acceptor string `json:"acceptor" binding:"notblank,entity"`
}

type CommandRaceRelation struct {
	CommandMeta
	Acceptor string `json:"acceptor" binding:"notblank,entity"`
	Relation string `json:"relation" binding:"oneof=WAR PEACE"`
}

type CommandShipClassCreate struct {
	CommandMeta
	Name     string  `json:"name" binding:"notblank,entity"`
	Drive    float64 `json:"drive" binding:"eq=0|gte=1"`
	Armament int     `json:"armament" binding:"ammoWeapons=Weapons"`
	Weapons  float64 `json:"weapons" binding:"ammoWeapons=Armament"`
	Shields  float64 `json:"shields" binding:"eq=0|gte=1"`
	Cargo    float64 `json:"cargo" binding:"eq=0|gte=1"`
}

type CommandShipClassMerge struct {
	CommandMeta
	Name   string `json:"name" binding:"notblank,entity,nefield=Target"`
	Target string `json:"target" binding:"notblank,entity,nefield=Name"`
}

type CommandShipClassRemove struct {
	CommandMeta
	Name string `json:"name" binding:"required,notblank,entity"`
}

type CommandShipGroupLoad struct {
	CommandMeta
	ID       string  `json:"id" binding:"required,uuid_rfc4122"`
	Cargo    string  `json:"cargo" binding:"oneof=COL MAT CAP"`
	Quantity float64 `json:"quantity" binding:"gte=0"`
}

type CommandShipGroupUnload struct {
	CommandMeta
	ID       string  `json:"id" binding:"required,uuid_rfc4122"`
	Quantity float64 `json:"quantity" binding:"gte=0"`
}

type CommandShipGroupSend struct {
	CommandMeta
	ID          string `json:"id" binding:"required,uuid_rfc4122"`
	Destination int    `json:"planetNumber" binding:"gte=0"`
}

type CommandShipGroupUpgrade struct {
	CommandMeta
	ID    string  `json:"id" binding:"required,uuid_rfc4122"`
	Tech  string  `json:"tech" binding:"oneof=ALL DRIVE WEAPONS SHIELDS CARGO"`
	Level float64 `json:"level" binding:"eq=0|gt=1"`
}

type CommandShipGroupMerge struct {
	CommandMeta
}

type CommandShipGroupBreak struct {
	CommandMeta
	ID       string `json:"id" binding:"uuid_rfc4122,nefield=NewID"`
	NewID    string `json:"newId" binding:"uuid_rfc4122,nefield=ID"`
	Quantity int    `json:"quantity" binding:"gte=0"`
}

type CommandShipGroupDismantle struct {
	CommandMeta
	ID string `json:"id" binding:"required,uuid_rfc4122"`
}

type CommandShipGroupTransfer struct {
	CommandMeta
	ID       string `json:"id" binding:"required,uuid_rfc4122"`
	Acceptor string `json:"acceptor" binding:"required,notblank,entity"`
}

type CommandShipGroupJoinFleet struct {
	CommandMeta
	ID   string `json:"id" binding:"required,uuid_rfc4122"`
	Name string `json:"name" binding:"required,notblank,entity"`
}

type CommandFleetMerge struct {
	CommandMeta
	Name   string `json:"name" binding:"required,notblank,entity,nefield=Target"`
	Target string `json:"target" binding:"required,notblank,entity,nefield=Name"`
}

type CommandFleetSend struct {
	CommandMeta
	Name        string `json:"name" binding:"required,notblank,entity"`
	Destination int    `json:"planetNumber" binding:"gte=0"`
}

type CommandScienceCreate struct {
	CommandMeta
	Name    string  `json:"name" binding:"required,notblank,entity"`
	Drive   float64 `json:"drive" binding:"gte=0,lte=1"`
	Weapons float64 `json:"weapons" binding:"gte=0,lte=1"`
	Shields float64 `json:"shields" binding:"gte=0,lte=1"`
	Cargo   float64 `json:"cargo" binding:"gte=0,lte=1"`
}

type CommandScienceRemove struct {
	CommandMeta
	Name string `json:"name" binding:"required,notblank,entity"`
}

type CommandPlanetRename struct {
	CommandMeta
	Number int    `json:"planetNumber" binding:"gte=0"`
	Name   string `json:"name" binding:"required,notblank,entity"`
}

type CommandPlanetProduce struct {
	CommandMeta
	Number     int    `json:"planetNumber" binding:"gte=0"`
	Production string `json:"production" binding:"oneof=MAT CAP DRIVE WEAPONS SHIELDS CARGO SCIENCE SHIP"`
	Subject    string `json:"subject" binding:"subject=Production"`
}

type CommandPlanetRouteSet struct {
	CommandMeta
	Origin      int    `json:"fromPlanetNumber" binding:"gte=0,nefield=Destination"`
	Destination int    `json:"toPlanetNumber" binding:"gte=0,nefield=Origin"`
	LoadType    string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
}

type CommandPlanetRouteRemove struct {
	CommandMeta
	Origin   int    `json:"fromPlanetNumber" binding:"gte=0"`
	LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
}
