package transcode

import (
	"context"

	"scrabble/gateway/internal/backendclient"
	fb "scrabble/pkg/fbs/scrabblefb"
)

// Message types: friends (incl. the one-time code path), per-user blocks,
// friend-game invitations, profile editing + email binding, statistics and GCG
// export. All are authenticated. Registered by registerSocialOps from NewRegistry.
const (
	MsgFriendsList       = "friends.list"
	MsgFriendsIncoming   = "friends.incoming"
	MsgFriendsOutgoing   = "friends.outgoing"
	MsgFriendRequest     = "friends.request"
	MsgFriendRespond     = "friends.respond"
	MsgFriendCancel      = "friends.cancel"
	MsgFriendUnfriend    = "friends.unfriend"
	MsgFriendCodeIssue   = "friends.code.issue"
	MsgFriendCodeRedeem  = "friends.code.redeem"
	MsgBlocksList        = "blocks.list"
	MsgBlockAdd          = "blocks.add"
	MsgBlockRemove       = "blocks.remove"
	MsgInvitationsList   = "invitation.list"
	MsgInvitationCreate  = "invitation.create"
	MsgInvitationAccept  = "invitation.accept"
	MsgInvitationDecline = "invitation.decline"
	MsgInvitationCancel  = "invitation.cancel"
	MsgProfileUpdate     = "profile.update"
	MsgStatsGet          = "stats.get"
	MsgGameGCG           = "game.gcg"
	MsgGameExportURL     = "game.export_url"
)

// registerSocialOps adds the social, account and history operations to the
// registry (all authenticated; the email-bind ops carry the costly-email flag).
func registerSocialOps(r *Registry, backend *backendclient.Client) {
	r.ops[MsgFriendsList] = Op{Handler: friendsListHandler(backend), Auth: true}
	r.ops[MsgFriendsIncoming] = Op{Handler: friendsIncomingHandler(backend), Auth: true}
	r.ops[MsgFriendsOutgoing] = Op{Handler: friendsOutgoingHandler(backend), Auth: true}
	r.ops[MsgFriendRequest] = Op{Handler: friendRequestHandler(backend), Auth: true}
	r.ops[MsgFriendRespond] = Op{Handler: friendRespondHandler(backend), Auth: true}
	r.ops[MsgFriendCancel] = Op{Handler: friendCancelHandler(backend), Auth: true}
	r.ops[MsgFriendUnfriend] = Op{Handler: friendUnfriendHandler(backend), Auth: true}
	r.ops[MsgFriendCodeIssue] = Op{Handler: friendCodeIssueHandler(backend), Auth: true}
	r.ops[MsgFriendCodeRedeem] = Op{Handler: friendCodeRedeemHandler(backend), Auth: true}
	r.ops[MsgBlocksList] = Op{Handler: blocksListHandler(backend), Auth: true}
	r.ops[MsgBlockAdd] = Op{Handler: blockAddHandler(backend), Auth: true}
	r.ops[MsgBlockRemove] = Op{Handler: blockRemoveHandler(backend), Auth: true}
	r.ops[MsgInvitationsList] = Op{Handler: invitationsListHandler(backend), Auth: true}
	r.ops[MsgInvitationCreate] = Op{Handler: invitationCreateHandler(backend), Auth: true}
	r.ops[MsgInvitationAccept] = Op{Handler: invitationRespondHandler(backend, true), Auth: true}
	r.ops[MsgInvitationDecline] = Op{Handler: invitationRespondHandler(backend, false), Auth: true}
	r.ops[MsgInvitationCancel] = Op{Handler: invitationCancelHandler(backend), Auth: true}
	r.ops[MsgProfileUpdate] = Op{Handler: profileUpdateHandler(backend), Auth: true}
	r.ops[MsgStatsGet] = Op{Handler: statsHandler(backend), Auth: true}
	r.ops[MsgGameGCG] = Op{Handler: gcgHandler(backend), Auth: true}
	r.ops[MsgGameExportURL] = Op{Handler: exportURLHandler(backend), Auth: true}
}

// --- friends ---

func friendsListHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.ListFriends(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeFriendList(res), nil
	}
}

func friendsIncomingHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.ListIncoming(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeIncomingList(res), nil
	}
}

func friendsOutgoingHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.ListOutgoing(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeOutgoingList(res), nil
	}
}

func friendRequestHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsTargetRequest(req.Payload, 0)
		// game_id is set only by an in-game request, so a disguised-robot opponent is recorded
		// per-game; it is empty for any non-game path.
		if err := backend.SendFriendRequest(ctx, req.UserID, string(in.AccountId()), string(in.GameId())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

func friendRespondHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsFriendRespondRequest(req.Payload, 0)
		if err := backend.RespondFriendRequest(ctx, req.UserID, string(in.RequesterId()), in.Accept()); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

func friendCancelHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsTargetRequest(req.Payload, 0)
		if err := backend.CancelFriendRequest(ctx, req.UserID, string(in.AccountId())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

func friendUnfriendHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsTargetRequest(req.Payload, 0)
		if err := backend.Unfriend(ctx, req.UserID, string(in.AccountId())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

func friendCodeIssueHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.IssueFriendCode(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeFriendCode(res), nil
	}
}

func friendCodeRedeemHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsRedeemCodeRequest(req.Payload, 0)
		res, err := backend.RedeemFriendCode(ctx, req.UserID, string(in.Code()))
		if err != nil {
			return nil, err
		}
		return encodeRedeemResult(res), nil
	}
}

// --- blocks ---

func blocksListHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.ListBlocks(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeBlockList(res), nil
	}
}

func blockAddHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsTargetRequest(req.Payload, 0)
		// game_id is set only by an in-game block, so a disguised-robot opponent is recorded
		// per-game; it is empty for a settings-screen block.
		if err := backend.Block(ctx, req.UserID, string(in.AccountId()), string(in.GameId())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

func blockRemoveHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsTargetRequest(req.Payload, 0)
		if err := backend.Unblock(ctx, req.UserID, string(in.AccountId())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

// --- invitations ---

func invitationsListHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.ListInvitations(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeInvitationList(res), nil
	}
}

func invitationCreateHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsCreateInvitationRequest(req.Payload, 0)
		params := backendclient.InvitationParams{
			InviteeIDs:      decodeInviteeIDs(in),
			Variant:         string(in.Variant()),
			TurnTimeoutSecs: int(in.TurnTimeoutSecs()),
			HintsAllowed:    in.HintsAllowed(),
			HintsPerPlayer:  int(in.HintsPerPlayer()),
			DropoutTiles:    string(in.DropoutTiles()),

			MultipleWordsPerTurn: in.MultipleWordsPerTurn(),
		}
		res, err := backend.CreateInvitation(ctx, req.UserID, params)
		if err != nil {
			return nil, err
		}
		return encodeInvitation(res), nil
	}
}

func invitationRespondHandler(backend *backendclient.Client, accept bool) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsInvitationActionRequest(req.Payload, 0)
		res, err := backend.RespondInvitation(ctx, req.UserID, string(in.InvitationId()), accept)
		if err != nil {
			return nil, err
		}
		return encodeInvitation(res), nil
	}
}

func invitationCancelHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsInvitationActionRequest(req.Payload, 0)
		if err := backend.CancelInvitation(ctx, req.UserID, string(in.InvitationId())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

// --- profile, email, stats, gcg ---

func profileUpdateHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsUpdateProfileRequest(req.Payload, 0)
		prefs := make([]string, in.VariantPreferencesLength())
		for i := range prefs {
			prefs[i] = string(in.VariantPreferences(i))
		}
		p := backendclient.ProfileResp{
			DisplayName:            string(in.DisplayName()),
			PreferredLanguage:      string(in.PreferredLanguage()),
			TimeZone:               string(in.TimeZone()),
			AwayStart:              string(in.AwayStart()),
			AwayEnd:                string(in.AwayEnd()),
			BlockChat:              in.BlockChat(),
			BlockFriendRequests:    in.BlockFriendRequests(),
			NotificationsInAppOnly: in.NotificationsInAppOnly(),
			VariantPreferences:     prefs,
		}
		out, err := backend.UpdateProfile(ctx, req.UserID, p)
		if err != nil {
			return nil, err
		}
		return encodeProfile(out), nil
	}
}

func statsHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.Stats(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeStats(res), nil
	}
}

func gcgHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsGameActionRequest(req.Payload, 0)
		res, err := backend.ExportGCG(ctx, req.UserID, string(in.GameId()))
		if err != nil {
			return nil, err
		}
		return encodeGcg(res), nil
	}
}

func exportURLHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsExportUrlRequest(req.Payload, 0)
		labels := make([]string, 0, in.ActionLabelsLength())
		for i := range in.ActionLabelsLength() {
			labels = append(labels, string(in.ActionLabels(i)))
		}
		res, err := backend.ExportURL(ctx, req.UserID, string(in.GameId()), string(in.Kind()), string(in.DateLocale()), string(in.TimeZone()), labels)
		if err != nil {
			return nil, err
		}
		return encodeExportURL(res), nil
	}
}

// decodeInviteeIDs reads the invitee id vector from a CreateInvitationRequest.
func decodeInviteeIDs(in *fb.CreateInvitationRequest) []string {
	n := in.InviteeIdsLength()
	out := make([]string, 0, n)
	for i := range n {
		out = append(out, string(in.InviteeIds(i)))
	}
	return out
}
