package backendclient

import (
	"context"

	"galaxy/gateway/internal/downstream"
	lobbymodel "galaxy/model/lobby"
	ordermodel "galaxy/model/order"
	reportmodel "galaxy/model/report"
	usermodel "galaxy/model/user"
)

// UserRoutes returns the authenticated `user.*` downstream routes
// served by backend. When client is nil every route resolves to a
// dependency-unavailable client so the static router still recognises
// the message types.
func UserRoutes(client *RESTClient) map[string]downstream.Client {
	target := downstream.Client(unavailableClient{})
	if client != nil {
		target = userCommandClient{rest: client}
	}
	return map[string]downstream.Client{
		usermodel.MessageTypeGetMyAccount:        target,
		usermodel.MessageTypeUpdateMyProfile:     target,
		usermodel.MessageTypeUpdateMySettings:    target,
		usermodel.MessageTypeListMySessions:      target,
		usermodel.MessageTypeRevokeMySession:     target,
		usermodel.MessageTypeRevokeAllMySessions: target,
	}
}

// LobbyRoutes returns the authenticated `lobby.*` downstream routes
// served by backend. When client is nil every route resolves to a
// dependency-unavailable client.
func LobbyRoutes(client *RESTClient) map[string]downstream.Client {
	target := downstream.Client(unavailableClient{})
	if client != nil {
		target = lobbyCommandClient{rest: client}
	}
	return map[string]downstream.Client{
		lobbymodel.MessageTypeMyGamesList:          target,
		lobbymodel.MessageTypePublicGamesList:      target,
		lobbymodel.MessageTypeMyApplicationsList:   target,
		lobbymodel.MessageTypeMyInvitesList:        target,
		lobbymodel.MessageTypeOpenEnrollment:       target,
		lobbymodel.MessageTypeGameCreate:           target,
		lobbymodel.MessageTypeApplicationSubmit:    target,
		lobbymodel.MessageTypeInviteRedeem:         target,
		lobbymodel.MessageTypeInviteDecline:        target,
	}
}

// GameRoutes returns the authenticated `user.games.*` downstream
// routes served by backend (which in turn forwards to the running
// game engine container). When client is nil every route resolves to
// a dependency-unavailable client.
func GameRoutes(client *RESTClient) map[string]downstream.Client {
	target := downstream.Client(unavailableClient{})
	if client != nil {
		target = gameCommandClient{rest: client}
	}
	return map[string]downstream.Client{
		ordermodel.MessageTypeUserGamesCommand:   target,
		ordermodel.MessageTypeUserGamesOrder:     target,
		ordermodel.MessageTypeUserGamesOrderGet:  target,
		reportmodel.MessageTypeUserGamesReport:   target,
	}
}

type unavailableClient struct{}

func (unavailableClient) ExecuteCommand(context.Context, downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
	return downstream.UnaryResult{}, downstream.ErrDownstreamUnavailable
}

type userCommandClient struct {
	rest *RESTClient
}

func (c userCommandClient) ExecuteCommand(ctx context.Context, command downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
	return c.rest.ExecuteUserCommand(ctx, command)
}

type lobbyCommandClient struct {
	rest *RESTClient
}

func (c lobbyCommandClient) ExecuteCommand(ctx context.Context, command downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
	return c.rest.ExecuteLobbyCommand(ctx, command)
}

type gameCommandClient struct {
	rest *RESTClient
}

func (c gameCommandClient) ExecuteCommand(ctx context.Context, command downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
	return c.rest.ExecuteGameCommand(ctx, command)
}

var (
	_ downstream.Client = unavailableClient{}
	_ downstream.Client = userCommandClient{}
	_ downstream.Client = lobbyCommandClient{}
	_ downstream.Client = gameCommandClient{}
)
