package router_test

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

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

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

func TestCommandRaceQuit(t *testing.T) {
	r := setupRouter()

	payload := &rest.Command{
		Actor: commandDefaultActor,
		Commands: []json.RawMessage{
			encodeCommand(&order.CommandRaceQuit{
				CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceQuit},
			}),
		},
	}

	w := httptest.NewRecorder()
	req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
	r.ServeHTTP(w, req)

	assert.Equal(t, commandNoErrorsStatus, w.Code, w.Body)

	// error: actor not set
	payload.Actor = ""
	w = httptest.NewRecorder()
	req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)

	payload.Actor = "       "
	w = httptest.NewRecorder()
	req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)

	// unrecognized command type
	payload.Commands = []json.RawMessage{
		encodeCommand(&order.CommandRaceQuit{
			CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandType("-unknown-")},
		}),
	}
	w = httptest.NewRecorder()
	req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)

	// error: no commands
	payload = &rest.Command{
		Actor: commandDefaultActor,
	}

	w = httptest.NewRecorder()
	req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
}

func TestCommandRaceVote(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		acceptor     string
	}{
		{commandNoErrorsStatus, "Valid request", "AnotherRace"},
		{http.StatusBadRequest, "Empty acceptor", ""},
		{http.StatusBadRequest, "Blank acceptor", "      "},
		{http.StatusBadRequest, "Invalid acceptor", "Race_👽"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandRaceVote{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote},
						Acceptor:    tc.acceptor,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandRaceRelation(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		relation     string
		acceptor     string
	}{
		{commandNoErrorsStatus, "Valid request 1", "WAR", "Opponent"},
		{commandNoErrorsStatus, "Valid request 2", "PEACE", "Opponent"},
		{http.StatusBadRequest, "Empty relation", "", "Opponent"},
		{http.StatusBadRequest, "Blank relation", "  ", "Opponent"},
		{http.StatusBadRequest, "Invalid relation", "Woina", "Opponent"},
		{http.StatusBadRequest, "Empty acceptor", "WAR", ""},
		{http.StatusBadRequest, "Blank acceptor", "WAR", "   "},
		{http.StatusBadRequest, "Invalid acceptor", "PEACE", "Race_👽"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandRaceRelation{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceRelation},
						Acceptor:    tc.acceptor,
						Relation:    tc.relation,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipClassCreate(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		D            float64
		A            int
		W, S, C      float64
		name         string
		expectStatus int
		description  string
	}{
		{1, 0, 0, 0, 0, "Drone", commandNoErrorsStatus, "Simple Drone"},
		{1, 1, 1, 0, 0, "Drone", commandNoErrorsStatus, "Armed Drone"},
		{1, 0, 0, 1, 0, "Drone", commandNoErrorsStatus, "Shielded Drone"},
		{1, 0, 0, 0, 1, "Drone", commandNoErrorsStatus, "Carrying Drone"},
		{1, 0, 0, 0, 0, "", http.StatusBadRequest, "Empty name"},
		{1, 0, 0, 0, 0, "   ", http.StatusBadRequest, "Blank name"},
		{1, 0, 0, 0, 0, "Drone🚀", http.StatusBadRequest, "Invalid name"},
		{-0.5, 0, 0, 0, 0, "Drone", http.StatusBadRequest, "Drive less than 0"},
		{0.9, 0, 0, 0, 0, "Drone", http.StatusBadRequest, "Drive less than 1"},
		{1, 1, 0, 0, 0, "Drone", http.StatusBadRequest, "Ammo without Weapons"},
		{1, 0, 1, 0, 0, "Drone", http.StatusBadRequest, "Weapons without Ammo"},
		{1, -1, 1, 0, 0, "Drone", http.StatusBadRequest, "Ammo less than 0"},
		{1, 1, 0.9, 0, 0, "Drone", http.StatusBadRequest, "Weapons less than 1"},
		{1, 1, -0.5, 0, 0, "Drone", http.StatusBadRequest, "Weapons less than 0"},
		{1, 0, 0, -0.5, 0, "Drone", http.StatusBadRequest, "Shields less than 0"},
		{1, 0, 0, 0.9, 0, "Drone", http.StatusBadRequest, "Shields less than 1"},
		{1, 0, 0, 0, -0.5, "Drone", http.StatusBadRequest, "Cargo less than 0"},
		{1, 0, 0, 0, 0.9, "Drone", http.StatusBadRequest, "Cargo less than 1"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipClassCreate{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassCreate},
						Name:        tc.name,
						Drive:       tc.D,
						Armament:    tc.A,
						Weapons:     tc.W,
						Shields:     tc.S,
						Cargo:       tc.C,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipClassMerge(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		name         string
		target       string
	}{
		{commandNoErrorsStatus, "Valid request", "Drone", "Spy"},
		{http.StatusBadRequest, "Empty name", "", "Spy"},
		{http.StatusBadRequest, "Blank name", "   ", "Spy"},
		{http.StatusBadRequest, "Invalid name", "Drone🚀", "Spy"},
		{http.StatusBadRequest, "Empty name", "Drone", " "},
		{http.StatusBadRequest, "Blank name", "Drone", "   "},
		{http.StatusBadRequest, "Invalid name", "Drone", "Spy🚀"},
		{http.StatusBadRequest, "Equal names", "Drone", "Drone"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipClassMerge{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassMerge},
						Name:        tc.name,
						Target:      tc.target,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipClassRemove(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		name         string
	}{
		{commandNoErrorsStatus, "Valid request", "Drone"},
		{http.StatusBadRequest, "Empty name", ""},
		{http.StatusBadRequest, "Blank name", "   "},
		{http.StatusBadRequest, "Invalid name", "Drone🚀"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipClassRemove{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassRemove},
						Name:        tc.name,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupBreak(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		newId        string
		quantity     int
	}{
		{commandNoErrorsStatus, "Valid request #1", validId1, validId2, 1},
		{commandNoErrorsStatus, "Valid request #2", validId1, validId2, 0},
		{http.StatusBadRequest, "Negative quantity", validId1, validId2, -1},
		{http.StatusBadRequest, "Empty id", "", validId2, 1},
		{http.StatusBadRequest, "Invalid id", invalidId, validId2, 1},
		{http.StatusBadRequest, "Empty newId", validId1, "", 1},
		{http.StatusBadRequest, "Invalid newId", validId1, invalidId, 1},
		{http.StatusBadRequest, "Equal id and newId", validId1, validId1, 1},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupBreak{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupBreak},
						ID:          tc.id,
						NewID:       tc.newId,
						Quantity:    tc.quantity,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupLoad(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		cargo        string
		quantity     float64
	}{
		{commandNoErrorsStatus, "Valid request #1", validId1, "COL", 0},
		{commandNoErrorsStatus, "Valid request #2", validId1, "MAT", 1},
		{commandNoErrorsStatus, "Valid request #2", validId1, "CAP", 2},
		{http.StatusBadRequest, "Invalid quantity", validId1, "COL", -0.5},
		{http.StatusBadRequest, "Empty cargo", validId1, "", 1},
		{http.StatusBadRequest, "Invalid cargo", validId1, "IND", 1},
		{http.StatusBadRequest, "Empty id", "", "COL", 1},
		{http.StatusBadRequest, "Invalid id", invalidId, "COL", 1},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupLoad{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupLoad},
						ID:          tc.id,
						Cargo:       tc.cargo,
						Quantity:    tc.quantity,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupUnload(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		quantity     float64
	}{
		{commandNoErrorsStatus, "Valid request #1", validId1, 0},
		{commandNoErrorsStatus, "Valid request #2", validId1, 1},
		{http.StatusBadRequest, "Invalid quantity", validId1, -0.5},
		{http.StatusBadRequest, "Empty id", "", 1},
		{http.StatusBadRequest, "Invalid id", invalidId, 1},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupUnload{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupUnload},
						ID:          tc.id,
						Quantity:    tc.quantity,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupSend(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		destination  int
	}{
		{commandNoErrorsStatus, "Valid request #1", validId1, 0},
		{commandNoErrorsStatus, "Valid request #1", validId1, 1},
		{http.StatusBadRequest, "Invalid destination", validId1, -1},
		{http.StatusBadRequest, "Empty id", "", 1},
		{http.StatusBadRequest, "Invalid id", invalidId, 1},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupSend{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupSend},
						ID:          tc.id,
						Destination: tc.destination,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupUpgrade(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		tech         string
		level        float64
	}{
		{commandNoErrorsStatus, "Valid request #1", validId1, "ALL", 0},
		{commandNoErrorsStatus, "Valid request #1", validId1, "DRIVE", 1.1},
		{commandNoErrorsStatus, "Valid request #1", validId1, "WEAPONS", 2.1},
		{commandNoErrorsStatus, "Valid request #1", validId1, "SHIELDS", 3.1},
		{commandNoErrorsStatus, "Valid request #1", validId1, "CARGO", 4.1},
		{http.StatusBadRequest, "Negative level", validId1, "DRIVE", -0.5},
		{http.StatusBadRequest, "Invalid level 0.5", validId1, "DRIVE", 0.5},
		{http.StatusBadRequest, "Invalid level 1.0", validId1, "DRIVE", 1.0},
		{http.StatusBadRequest, "Empty id", "", "ALL", 0},
		{http.StatusBadRequest, "Invalid id", invalidId, "ALL", 0},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupUpgrade{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupUpgrade},
						ID:          tc.id,
						Tech:        tc.tech,
						Level:       tc.level,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupMerge(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
	}{
		{commandNoErrorsStatus, "Valid request"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupMerge{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupMerge},
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupDismantle(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
	}{
		{commandNoErrorsStatus, "Valid request", validId1},
		{http.StatusBadRequest, "Empty id", ""},
		{http.StatusBadRequest, "Invalid id", invalidId},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupDismantle{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupDismantle},
						ID:          tc.id,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupTransfer(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		acceptor     string
	}{
		{commandNoErrorsStatus, "Valid request", validId1, "AnotherRace"},
		{http.StatusBadRequest, "Blank id", "", "AnotherRace"},
		{http.StatusBadRequest, "Invalid id", invalidId, "AnotherRace"},
		{http.StatusBadRequest, "Empty acceptor", validId1, ""},
		{http.StatusBadRequest, "Blank acceptor", validId1, "      "},
		{http.StatusBadRequest, "Invalid acceptor", validId1, "Race_👽"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupTransfer{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupTransfer},
						ID:          tc.id,
						Acceptor:    tc.acceptor,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandShipGroupJoinFleet(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		id           string
		name         string
	}{
		{commandNoErrorsStatus, "Valid request", validId1, "AnotherRace"},
		{http.StatusBadRequest, "Blank id", "", "AnotherRace"},
		{http.StatusBadRequest, "Invalid id", invalidId, "AnotherRace"},
		{http.StatusBadRequest, "Empty name", validId1, ""},
		{http.StatusBadRequest, "Blank name", validId1, "      "},
		{http.StatusBadRequest, "Invalid name", validId1, "Fleet_🚢"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandShipGroupJoinFleet{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupJoinFleet},
						ID:          tc.id,
						Name:        tc.name,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandFleetMerge(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		name         string
		target       string
	}{
		{commandNoErrorsStatus, "Valid request", "Fleet", "Bomber"},
		{http.StatusBadRequest, "Empty name", "", "Bomber"},
		{http.StatusBadRequest, "Invalid name", "Fleet_🚢", "Bomber"},
		{http.StatusBadRequest, "Empty target", "Fleet", ""},
		{http.StatusBadRequest, "Invalid target", "Fleet", "Bomber_🚢"},
		{http.StatusBadRequest, "Equal name and target", "Fleet", "Fleet"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandFleetMerge{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeFleetMerge},
						Name:        tc.name,
						Target:      tc.target,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandFleetSend(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		name         string
		destination  int
	}{
		{commandNoErrorsStatus, "Valid request #1", "Fleet", 0},
		{commandNoErrorsStatus, "Valid request #2", "Fleet", 1},
		{http.StatusBadRequest, "Invalid destination", "Fleet", -1},
		{http.StatusBadRequest, "Empty name", "", 1},
		{http.StatusBadRequest, "Invalid name", "Fleet_🚢", 1},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandFleetSend{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeFleetSend},
						Name:        tc.name,
						Destination: tc.destination,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandScienceCreate(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		D, W, S, C   float64
		name         string
	}{
		{commandNoErrorsStatus, "Valid request", 0.25, 0.25, 0.25, 0.25, "Science"},
		{http.StatusBadRequest, "Empty name", 0.25, 0.25, 0.25, 0.25, ""},
		{http.StatusBadRequest, "Invalid name", 0.25, 0.25, 0.25, 0.25, "Science🧪"},
		{http.StatusBadRequest, "Negative drive", -.5, 0.25, 0.25, 0.25, "Science"},
		{http.StatusBadRequest, "Negative weapons", 0.25, -.5, 0.25, 0.25, "Science"},
		{http.StatusBadRequest, "Negative shields", 0.25, 0.25, -.5, 0.25, "Science"},
		{http.StatusBadRequest, "Negative cargo", 0.25, 0.25, 0.25, -.5, "Science"},
		{http.StatusBadRequest, "Too big drive", 1.1, 0.25, 0.25, 0.25, "Science"},
		{http.StatusBadRequest, "Too big weapons", 0.25, 1.05, 0.25, 0.25, "Science"},
		{http.StatusBadRequest, "Too big shields", 0.25, 0.25, 1.5, 0.25, "Science"},
		{http.StatusBadRequest, "Too big cargo", 0.25, 0.25, 0.25, 1.01, "Science"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandScienceCreate{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeScienceCreate},
						Name:        tc.name,
						Drive:       tc.D,
						Weapons:     tc.W,
						Shields:     tc.S,
						Cargo:       tc.C,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandScienceRemove(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		name         string
	}{
		{commandNoErrorsStatus, "Valid request", "Drone"},
		{http.StatusBadRequest, "Empty name", ""},
		{http.StatusBadRequest, "Blank name", "   "},
		{http.StatusBadRequest, "Invalid name", "Science🧪"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandScienceRemove{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeScienceRemove},
						Name:        tc.name,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandPlanetRename(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		number       int
		name         string
	}{
		{commandNoErrorsStatus, "Valid request #1", 0, "HW"},
		{commandNoErrorsStatus, "Valid request #2", 1, "HW"},
		{http.StatusBadRequest, "Invalid number", -1, "HW"},
		{http.StatusBadRequest, "Empty name", 1, ""},
		{http.StatusBadRequest, "Blank name", 1, "   "},
		{http.StatusBadRequest, "Invalid name", 1, "Planet🪐"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandPlanetRename{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRename},
						Number:      tc.number,
						Name:        tc.name,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandPlanetProduce(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus        int
		description         string
		number              int
		production, subject string
	}{
		{commandNoErrorsStatus, "Valid request MAT", 0, "MAT", ""},
		{commandNoErrorsStatus, "Valid request CAP", 1, "CAP", ""},
		{commandNoErrorsStatus, "Valid request DRIVE", 2, "DRIVE", ""},
		{commandNoErrorsStatus, "Valid request WEAPONS", 3, "WEAPONS", ""},
		{commandNoErrorsStatus, "Valid request SHIELDS", 4, "SHIELDS", ""},
		{commandNoErrorsStatus, "Valid request CARGO", 5, "CARGO", ""},
		{commandNoErrorsStatus, "Valid request SCIENCE", 6, "SCIENCE", "Science"},
		{commandNoErrorsStatus, "Valid request SHIP", 7, "SHIP", "Ship"},
		{http.StatusBadRequest, "Empty production", 0, "", ""},
		{http.StatusBadRequest, "Invalid production", 0, "IND", ""},
		{http.StatusBadRequest, "Invalid planet", -1, "DRIVE", ""},
		{http.StatusBadRequest, "Empty science subject", 6, "SCIENCE", ""},
		{http.StatusBadRequest, "Invalid science subject", 6, "SCIENCE", "Science🧪"},
		{http.StatusBadRequest, "Empty ship subject", 6, "SHIP", ""},
		{http.StatusBadRequest, "Invalid ship subject", 6, "SHIP", "Ship🚀"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandPlanetProduce{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetProduce},
						Number:      tc.number,
						Production:  tc.production,
						Subject:     tc.subject,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandPlanetRouteSet(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus        int
		description         string
		origin, destination int
		loadType            string
	}{
		{commandNoErrorsStatus, "Valid request MAT", 1, 0, "MAT"},
		{commandNoErrorsStatus, "Valid request CAP", 0, 1, "CAP"},
		{commandNoErrorsStatus, "Valid request COL", 1, 2, "COL"},
		{commandNoErrorsStatus, "Valid request EMP", 3, 0, "EMP"},
		{http.StatusBadRequest, "Empty loadType", 0, 1, ""},
		{http.StatusBadRequest, "Invalid loadType", 0, 1, "IND"},
		{http.StatusBadRequest, "Invalid origin", -1, 1, "MAT"},
		{http.StatusBadRequest, "Invalid destination", 1, -1, "MAT"},
		{http.StatusBadRequest, "Origin equals destination", 1, 1, "COL"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandPlanetRouteSet{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRouteSet},
						Origin:      tc.origin,
						Destination: tc.destination,
						LoadType:    tc.loadType,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestCommandPlanetRouteRemove(t *testing.T) {
	r := setupRouter()

	for _, tc := range []struct {
		expectStatus int
		description  string
		origin       int
		loadType     string
	}{
		{commandNoErrorsStatus, "Valid request MAT", 0, "MAT"},
		{commandNoErrorsStatus, "Valid request CAP", 1, "CAP"},
		{commandNoErrorsStatus, "Valid request COL", 2, "COL"},
		{commandNoErrorsStatus, "Valid request EMP", 0, "EMP"},
		{http.StatusBadRequest, "Empty loadType", 1, ""},
		{http.StatusBadRequest, "Invalid loadType", 1, "IND"},
		{http.StatusBadRequest, "Invalid origin", -1, "MAT"},
	} {
		t.Run(tc.description, func(t *testing.T) {
			payload := &rest.Command{
				Actor: commandDefaultActor,
				Commands: []json.RawMessage{
					encodeCommand(&order.CommandPlanetRouteRemove{
						CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRouteRemove},
						Origin:      tc.origin,
						LoadType:    tc.loadType,
					}),
				},
			}

			w := httptest.NewRecorder()
			req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
			r.ServeHTTP(w, req)

			assert.Equal(t, tc.expectStatus, w.Code, w.Body)
		})
	}
}

func TestMultipleCommands(t *testing.T) {
	e := newExecutor()
	r := setupRouterExecutor(e)

	payload := &rest.Command{
		Actor: commandDefaultActor,
		Commands: []json.RawMessage{
			encodeCommand(&order.CommandRaceRelation{
				CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceRelation},
				Acceptor:    "Opponent",
				Relation:    "PEACE",
			}),
			encodeCommand(&order.CommandRaceVote{
				CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote},
				Acceptor:    "Opponent",
			}),
		},
	}

	w := httptest.NewRecorder()
	req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload))
	r.ServeHTTP(w, req)

	assert.Equal(t, commandNoErrorsStatus, w.Code, w.Body)

	assert.Equal(t, 2, e.(*dummyExecutor).CommandsExecuted)
}
