package handler

import (
	"encoding/json"
	"fmt"
	"net/http"

	"galaxy/game/internal/controller"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"

	"galaxy/model/order"
	"galaxy/model/rest"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

func CommandHandler(c *gin.Context, executor CommandExecutor) {
	var cmd rest.Command
	if errorResponse(c, c.ShouldBindJSON(&cmd)) {
		return
	}

	commands := make([]Command, len(cmd.Commands))
	for i := range cmd.Commands {
		command, err := parseCommand(cmd.Actor, cmd.Commands[i])
		if errorResponse(c, err) {
			return
		}
		commands[i] = command
	}
	if len(commands) == 0 {
		// `PUT /api/v1/command` is the immediate-execution path —
		// running an empty batch is a meaningless no-op, so we
		// reject it with `400` rather than rely on the validator.
		// `PUT /api/v1/order` keeps an empty list (the player
		// cleared their draft) — see `OrderHandler`.
		c.JSON(http.StatusBadRequest, gin.H{"error": "no commands given"})
		return
	}

	if errorResponse(c, executor.Execute(commands...)) {
		return
	}

	c.Status(http.StatusAccepted)
}

func parseCommand(actor string, c json.RawMessage) (Command, error) {
	meta := new(order.CommandMeta)
	if err := json.Unmarshal(c, meta); err != nil {
		return nil, err
	}
	switch t := meta.CmdType; t {
	case order.CommandTypeRaceQuit:
		return commandRaceQuit(actor)
	case order.CommandTypeRaceVote:
		return commandRaceVote(actor, c)
	case order.CommandTypeRaceRelation:
		return commandRaceRelation(actor, c)
	case order.CommandTypeShipClassCreate:
		return commandShipClassCreate(actor, c)
	case order.CommandTypeShipClassMerge:
		return commandShipClassMerge(actor, c)
	case order.CommandTypeShipClassRemove:
		return commandShipClassRemove(actor, c)
	case order.CommandTypeShipGroupBreak:
		return commandShipGroupBreak(actor, c)
	case order.CommandTypeShipGroupLoad:
		return commandShipGroupLoad(actor, c)
	case order.CommandTypeShipGroupUnload:
		return commandShipGroupUnload(actor, c)
	case order.CommandTypeShipGroupSend:
		return commandShipGroupSend(actor, c)
	case order.CommandTypeShipGroupUpgrade:
		return commandShipGroupUpgrade(actor, c)
	case order.CommandTypeShipGroupMerge:
		return commandShipGroupMerge(actor, c)
	case order.CommandTypeShipGroupDismantle:
		return commandShipGroupDismantle(actor, c)
	case order.CommandTypeShipGroupTransfer:
		return commandShipGroupTransfer(actor, c)
	case order.CommandTypeShipGroupJoinFleet:
		return commandShipGroupJoinFleet(actor, c)
	case order.CommandTypeFleetMerge:
		return commandFleetMerge(actor, c)
	case order.CommandTypeFleetSend:
		return commandFleetSend(actor, c)
	case order.CommandTypeScienceCreate:
		return commandScienceCreate(actor, c)
	case order.CommandTypeScienceRemove:
		return commandScienceRemove(actor, c)
	case order.CommandTypePlanetRename:
		return commandPlanetRename(actor, c)
	case order.CommandTypePlanetProduce:
		return commandPlanetProduce(actor, c)
	case order.CommandTypePlanetRouteSet:
		return commandPlanetRouteSet(actor, c)
	case order.CommandTypePlanetRouteRemove:
		return commandPlanetRouteRemove(actor, c)
	default:
		return nil, fmt.Errorf("unknown comman type: %s", t)
	}
}

func commandRaceQuit(actor string) (Command, error) {
	return func(c controller.Ctrl) error { return c.RaceQuit(actor) }, nil
}

func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandRaceVote)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.RaceVote(actor, v.Acceptor)
		}, nil
	}
}

func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandRaceRelation)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.RaceRelation(actor, v.Acceptor, v.Relation)
		}, nil
	}
}

func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipClassCreate)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipClassCreate(actor, v.Name, v.Drive, int(v.Armament), v.Weapons, v.Shields, v.Cargo)
		}, nil
	}
}

func commandShipClassMerge(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipClassMerge)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipClassMerge(actor, v.Name, v.Target)
		}, nil
	}
}

func commandShipClassRemove(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipClassRemove)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipClassRemove(actor, v.Name)
		}, nil
	}
}

func commandShipGroupBreak(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupBreak)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupBreak(actor, uuid.MustParse(v.ID), uuid.MustParse(v.NewID), uint(v.Quantity))
		}, nil
	}
}

func commandShipGroupLoad(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupLoad)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupLoad(actor, uuid.MustParse(v.ID), v.Cargo, v.Quantity)
		}, nil
	}
}

func commandShipGroupUnload(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupUnload)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupUnload(actor, uuid.MustParse(v.ID), v.Quantity)
		}, nil
	}
}

func commandShipGroupSend(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupSend)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupSend(actor, uuid.MustParse(v.ID), uint(v.Destination))
		}, nil
	}
}

func commandShipGroupUpgrade(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupUpgrade)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupUpgrade(actor, uuid.MustParse(v.ID), v.Tech, v.Level)
		}, nil
	}
}

func commandShipGroupMerge(actor string, c json.RawMessage) (Command, error) {
	return func(c controller.Ctrl) error {
		return c.ShipGroupMerge(actor)
	}, nil
}

func commandShipGroupDismantle(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupDismantle)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupDismantle(actor, uuid.MustParse(v.ID))
		}, nil
	}
}

func commandShipGroupTransfer(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupTransfer)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupTransfer(actor, v.Acceptor, uuid.MustParse(v.ID))
		}, nil
	}
}

func commandShipGroupJoinFleet(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandShipGroupJoinFleet)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ShipGroupJoinFleet(actor, v.Name, uuid.MustParse(v.ID))
		}, nil
	}
}

func commandFleetMerge(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandFleetMerge)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.FleetMerge(actor, v.Name, v.Target)
		}, nil
	}
}

func commandFleetSend(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandFleetSend)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.FleetSend(actor, v.Name, uint(v.Destination))
		}, nil
	}
}

func commandScienceCreate(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandScienceCreate)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ScienceCreate(actor, v.Name, v.Drive, v.Weapons, v.Shields, v.Cargo)
		}, nil
	}
}

func commandScienceRemove(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandScienceRemove)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.ScienceRemove(actor, v.Name)
		}, nil
	}
}

func commandPlanetRename(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandPlanetRename)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.PlanetRename(actor, v.Number, v.Name)
		}, nil
	}
}

func commandPlanetProduce(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandPlanetProduce)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.PlanetProduce(actor, v.Number, v.Production, v.Subject)
		}, nil
	}
}

func commandPlanetRouteSet(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandPlanetRouteSet)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.PlanetRouteSet(actor, v.LoadType, uint(v.Origin), uint(v.Destination))
		}, nil
	}
}

func commandPlanetRouteRemove(actor string, c json.RawMessage) (Command, error) {
	if v, err := unmarshallCommand(c, new(order.CommandPlanetRouteRemove)); err != nil {
		return nil, err
	} else {
		return func(c controller.Ctrl) error {
			return c.PlanetRouteRemove(actor, v.LoadType, uint(v.Origin))
		}, nil
	}
}

// Helpers

func unmarshallCommand[T order.DecodableCommand](c json.RawMessage, v T) (T, error) {
	if err := json.Unmarshal(c, v); err != nil {
		return v, err
	}
	if err := validateCommand(v); err != nil {
		return v, err
	}
	return v, nil
}

func validateCommand(v order.DecodableCommand) error {
	if ve, ok := binding.Validator.Engine().(*validator.Validate); ok {
		if err := ve.Struct(v); err != nil {
			return err
		}
	}
	return nil
}
