package transcoder

import (
	"errors"
	"fmt"
	"time"

	usermodel "galaxy/model/user"
	userfbs "galaxy/schema/fbs/user"

	flatbuffers "github.com/google/flatbuffers/go"
)

// GetMyAccountRequestToPayload converts usermodel.GetMyAccountRequest to
// FlatBuffers bytes suitable for the authenticated gateway transport.
func GetMyAccountRequestToPayload(request *usermodel.GetMyAccountRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode get my account request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	userfbs.GetMyAccountRequestStart(builder)
	offset := userfbs.GetMyAccountRequestEnd(builder)
	userfbs.FinishGetMyAccountRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToGetMyAccountRequest converts FlatBuffers payload bytes into
// usermodel.GetMyAccountRequest.
func PayloadToGetMyAccountRequest(data []byte) (result *usermodel.GetMyAccountRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode get my account request payload: data is empty")
	}

	defer recoverUserDecodePanic("decode get my account request payload", &result, &err)

	_ = userfbs.GetRootAsGetMyAccountRequest(data, 0)
	return &usermodel.GetMyAccountRequest{}, nil
}

// UpdateMyProfileRequestToPayload converts usermodel.UpdateMyProfileRequest to
// FlatBuffers bytes suitable for the authenticated gateway transport.
func UpdateMyProfileRequestToPayload(request *usermodel.UpdateMyProfileRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode update my profile request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(128)
	displayName := builder.CreateString(request.DisplayName)

	userfbs.UpdateMyProfileRequestStart(builder)
	userfbs.UpdateMyProfileRequestAddDisplayName(builder, displayName)
	offset := userfbs.UpdateMyProfileRequestEnd(builder)
	userfbs.FinishUpdateMyProfileRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToUpdateMyProfileRequest converts FlatBuffers payload bytes into
// usermodel.UpdateMyProfileRequest.
func PayloadToUpdateMyProfileRequest(data []byte) (result *usermodel.UpdateMyProfileRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode update my profile request payload: data is empty")
	}

	defer recoverUserDecodePanic("decode update my profile request payload", &result, &err)

	request := userfbs.GetRootAsUpdateMyProfileRequest(data, 0)
	return &usermodel.UpdateMyProfileRequest{
		DisplayName: string(request.DisplayName()),
	}, nil
}

// UpdateMySettingsRequestToPayload converts
// usermodel.UpdateMySettingsRequest to FlatBuffers bytes suitable for the
// authenticated gateway transport.
func UpdateMySettingsRequestToPayload(request *usermodel.UpdateMySettingsRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode update my settings request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(128)
	preferredLanguage := builder.CreateString(request.PreferredLanguage)
	timeZone := builder.CreateString(request.TimeZone)

	userfbs.UpdateMySettingsRequestStart(builder)
	userfbs.UpdateMySettingsRequestAddPreferredLanguage(builder, preferredLanguage)
	userfbs.UpdateMySettingsRequestAddTimeZone(builder, timeZone)
	offset := userfbs.UpdateMySettingsRequestEnd(builder)
	userfbs.FinishUpdateMySettingsRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToUpdateMySettingsRequest converts FlatBuffers payload bytes into
// usermodel.UpdateMySettingsRequest.
func PayloadToUpdateMySettingsRequest(data []byte) (result *usermodel.UpdateMySettingsRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode update my settings request payload: data is empty")
	}

	defer recoverUserDecodePanic("decode update my settings request payload", &result, &err)

	request := userfbs.GetRootAsUpdateMySettingsRequest(data, 0)
	return &usermodel.UpdateMySettingsRequest{
		PreferredLanguage: string(request.PreferredLanguage()),
		TimeZone:          string(request.TimeZone()),
	}, nil
}

// AccountResponseToPayload converts usermodel.AccountResponse to FlatBuffers
// bytes suitable for the authenticated gateway transport.
func AccountResponseToPayload(response *usermodel.AccountResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode account response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(512)
	accountOffset, err := encodeAccount(builder, response.Account)
	if err != nil {
		return nil, fmt.Errorf("encode account response payload: %w", err)
	}

	userfbs.AccountResponseStart(builder)
	userfbs.AccountResponseAddAccount(builder, accountOffset)
	offset := userfbs.AccountResponseEnd(builder)
	userfbs.FinishAccountResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToAccountResponse converts FlatBuffers payload bytes into
// usermodel.AccountResponse.
func PayloadToAccountResponse(data []byte) (result *usermodel.AccountResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode account response payload: data is empty")
	}

	defer recoverUserDecodePanic("decode account response payload", &result, &err)

	response := userfbs.GetRootAsAccountResponse(data, 0)
	account := response.Account(nil)
	if account == nil {
		return nil, errors.New("decode account response payload: account is missing")
	}

	decodedAccount, err := decodeAccount(account)
	if err != nil {
		return nil, fmt.Errorf("decode account response payload: %w", err)
	}

	return &usermodel.AccountResponse{Account: decodedAccount}, nil
}

// ErrorResponseToPayload converts usermodel.ErrorResponse to FlatBuffers bytes
// suitable for the authenticated gateway transport.
func ErrorResponseToPayload(response *usermodel.ErrorResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode error response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(128)
	errorOffset := encodeErrorBody(builder, response.Error)

	userfbs.ErrorResponseStart(builder)
	userfbs.ErrorResponseAddError(builder, errorOffset)
	offset := userfbs.ErrorResponseEnd(builder)
	userfbs.FinishErrorResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToErrorResponse converts FlatBuffers payload bytes into
// usermodel.ErrorResponse.
func PayloadToErrorResponse(data []byte) (result *usermodel.ErrorResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode error response payload: data is empty")
	}

	defer recoverUserDecodePanic("decode error response payload", &result, &err)

	response := userfbs.GetRootAsErrorResponse(data, 0)
	errorBody := response.Error(nil)
	if errorBody == nil {
		return nil, errors.New("decode error response payload: error is missing")
	}

	return &usermodel.ErrorResponse{
		Error: usermodel.ErrorBody{
			Code:    string(errorBody.Code()),
			Message: string(errorBody.Message()),
		},
	}, nil
}

func encodeAccount(builder *flatbuffers.Builder, account usermodel.Account) (flatbuffers.UOffsetT, error) {
	entitlementOffset, err := encodeEntitlementSnapshot(builder, account.Entitlement)
	if err != nil {
		return 0, fmt.Errorf("encode account: %w", err)
	}

	activeSanctionOffsets := make([]flatbuffers.UOffsetT, len(account.ActiveSanctions))
	for index := range account.ActiveSanctions {
		activeSanctionOffsets[index], err = encodeActiveSanction(builder, account.ActiveSanctions[index])
		if err != nil {
			return 0, fmt.Errorf("encode account active sanction %d: %w", index, err)
		}
	}

	var activeSanctionsVector flatbuffers.UOffsetT
	if len(activeSanctionOffsets) > 0 {
		userfbs.AccountViewStartActiveSanctionsVector(builder, len(activeSanctionOffsets))
		for index := len(activeSanctionOffsets) - 1; index >= 0; index-- {
			builder.PrependUOffsetT(activeSanctionOffsets[index])
		}
		activeSanctionsVector = builder.EndVector(len(activeSanctionOffsets))
	}

	activeLimitOffsets := make([]flatbuffers.UOffsetT, len(account.ActiveLimits))
	for index := range account.ActiveLimits {
		activeLimitOffsets[index], err = encodeActiveLimit(builder, account.ActiveLimits[index])
		if err != nil {
			return 0, fmt.Errorf("encode account active limit %d: %w", index, err)
		}
	}

	var activeLimitsVector flatbuffers.UOffsetT
	if len(activeLimitOffsets) > 0 {
		userfbs.AccountViewStartActiveLimitsVector(builder, len(activeLimitOffsets))
		for index := len(activeLimitOffsets) - 1; index >= 0; index-- {
			builder.PrependUOffsetT(activeLimitOffsets[index])
		}
		activeLimitsVector = builder.EndVector(len(activeLimitOffsets))
	}

	userID := builder.CreateString(account.UserID)
	email := builder.CreateString(account.Email)
	userName := builder.CreateString(account.UserName)
	var displayName flatbuffers.UOffsetT
	if account.DisplayName != "" {
		displayName = builder.CreateString(account.DisplayName)
	}
	preferredLanguage := builder.CreateString(account.PreferredLanguage)
	timeZone := builder.CreateString(account.TimeZone)

	var declaredCountry flatbuffers.UOffsetT
	if account.DeclaredCountry != "" {
		declaredCountry = builder.CreateString(account.DeclaredCountry)
	}

	userfbs.AccountViewStart(builder)
	userfbs.AccountViewAddUserId(builder, userID)
	userfbs.AccountViewAddEmail(builder, email)
	userfbs.AccountViewAddUserName(builder, userName)
	if displayName != 0 {
		userfbs.AccountViewAddDisplayName(builder, displayName)
	}
	userfbs.AccountViewAddPreferredLanguage(builder, preferredLanguage)
	userfbs.AccountViewAddTimeZone(builder, timeZone)
	if declaredCountry != 0 {
		userfbs.AccountViewAddDeclaredCountry(builder, declaredCountry)
	}
	userfbs.AccountViewAddEntitlement(builder, entitlementOffset)
	if activeSanctionsVector != 0 {
		userfbs.AccountViewAddActiveSanctions(builder, activeSanctionsVector)
	}
	if activeLimitsVector != 0 {
		userfbs.AccountViewAddActiveLimits(builder, activeLimitsVector)
	}
	userfbs.AccountViewAddCreatedAtMs(builder, account.CreatedAt.UTC().UnixMilli())
	userfbs.AccountViewAddUpdatedAtMs(builder, account.UpdatedAt.UTC().UnixMilli())

	return userfbs.AccountViewEnd(builder), nil
}

func decodeAccount(account *userfbs.AccountView) (usermodel.Account, error) {
	entitlement := account.Entitlement(nil)
	if entitlement == nil {
		return usermodel.Account{}, errors.New("account entitlement is missing")
	}

	decodedEntitlement, err := decodeEntitlementSnapshot(entitlement)
	if err != nil {
		return usermodel.Account{}, fmt.Errorf("decode account entitlement: %w", err)
	}

	createdAt := time.UnixMilli(account.CreatedAtMs()).UTC()
	updatedAt := time.UnixMilli(account.UpdatedAtMs()).UTC()

	result := usermodel.Account{
		UserID:            string(account.UserId()),
		Email:             string(account.Email()),
		UserName:          string(account.UserName()),
		DisplayName:       string(account.DisplayName()),
		PreferredLanguage: string(account.PreferredLanguage()),
		TimeZone:          string(account.TimeZone()),
		DeclaredCountry:   string(account.DeclaredCountry()),
		Entitlement:       decodedEntitlement,
		ActiveSanctions:   make([]usermodel.ActiveSanction, 0, account.ActiveSanctionsLength()),
		ActiveLimits:      make([]usermodel.ActiveLimit, 0, account.ActiveLimitsLength()),
		CreatedAt:         createdAt,
		UpdatedAt:         updatedAt,
	}

	activeSanction := new(userfbs.ActiveSanction)
	for index := 0; index < account.ActiveSanctionsLength(); index++ {
		if !account.ActiveSanctions(activeSanction, index) {
			return usermodel.Account{}, fmt.Errorf("account active sanction %d is missing", index)
		}

		decodedSanction, err := decodeActiveSanction(activeSanction)
		if err != nil {
			return usermodel.Account{}, fmt.Errorf("decode account active sanction %d: %w", index, err)
		}
		result.ActiveSanctions = append(result.ActiveSanctions, decodedSanction)
	}

	activeLimit := new(userfbs.ActiveLimit)
	for index := 0; index < account.ActiveLimitsLength(); index++ {
		if !account.ActiveLimits(activeLimit, index) {
			return usermodel.Account{}, fmt.Errorf("account active limit %d is missing", index)
		}

		decodedLimit, err := decodeActiveLimit(activeLimit)
		if err != nil {
			return usermodel.Account{}, fmt.Errorf("decode account active limit %d: %w", index, err)
		}
		result.ActiveLimits = append(result.ActiveLimits, decodedLimit)
	}

	return result, nil
}

func encodeEntitlementSnapshot(builder *flatbuffers.Builder, snapshot usermodel.EntitlementSnapshot) (flatbuffers.UOffsetT, error) {
	actorOffset := encodeActorRef(builder, snapshot.Actor)
	planCode := builder.CreateString(snapshot.PlanCode)
	source := builder.CreateString(snapshot.Source)
	reasonCode := builder.CreateString(snapshot.ReasonCode)

	userfbs.EntitlementSnapshotStart(builder)
	userfbs.EntitlementSnapshotAddPlanCode(builder, planCode)
	userfbs.EntitlementSnapshotAddIsPaid(builder, snapshot.IsPaid)
	userfbs.EntitlementSnapshotAddSource(builder, source)
	userfbs.EntitlementSnapshotAddActor(builder, actorOffset)
	userfbs.EntitlementSnapshotAddReasonCode(builder, reasonCode)
	userfbs.EntitlementSnapshotAddStartsAtMs(builder, snapshot.StartsAt.UTC().UnixMilli())
	if snapshot.EndsAt != nil {
		userfbs.EntitlementSnapshotAddEndsAtMs(builder, snapshot.EndsAt.UTC().UnixMilli())
	}
	userfbs.EntitlementSnapshotAddUpdatedAtMs(builder, snapshot.UpdatedAt.UTC().UnixMilli())

	return userfbs.EntitlementSnapshotEnd(builder), nil
}

func decodeEntitlementSnapshot(snapshot *userfbs.EntitlementSnapshot) (usermodel.EntitlementSnapshot, error) {
	actor := snapshot.Actor(nil)
	if actor == nil {
		return usermodel.EntitlementSnapshot{}, errors.New("entitlement actor is missing")
	}

	decodedActor, err := decodeActorRef(actor)
	if err != nil {
		return usermodel.EntitlementSnapshot{}, fmt.Errorf("decode entitlement actor: %w", err)
	}

	return usermodel.EntitlementSnapshot{
		PlanCode:   string(snapshot.PlanCode()),
		IsPaid:     snapshot.IsPaid(),
		Source:     string(snapshot.Source()),
		Actor:      decodedActor,
		ReasonCode: string(snapshot.ReasonCode()),
		StartsAt:   time.UnixMilli(snapshot.StartsAtMs()).UTC(),
		EndsAt:     optionalUnixMilli(snapshot.EndsAtMs()),
		UpdatedAt:  time.UnixMilli(snapshot.UpdatedAtMs()).UTC(),
	}, nil
}

func encodeActiveSanction(builder *flatbuffers.Builder, sanction usermodel.ActiveSanction) (flatbuffers.UOffsetT, error) {
	actorOffset := encodeActorRef(builder, sanction.Actor)
	sanctionCode := builder.CreateString(sanction.SanctionCode)
	scope := builder.CreateString(sanction.Scope)
	reasonCode := builder.CreateString(sanction.ReasonCode)

	userfbs.ActiveSanctionStart(builder)
	userfbs.ActiveSanctionAddSanctionCode(builder, sanctionCode)
	userfbs.ActiveSanctionAddScope(builder, scope)
	userfbs.ActiveSanctionAddReasonCode(builder, reasonCode)
	userfbs.ActiveSanctionAddActor(builder, actorOffset)
	userfbs.ActiveSanctionAddAppliedAtMs(builder, sanction.AppliedAt.UTC().UnixMilli())
	if sanction.ExpiresAt != nil {
		userfbs.ActiveSanctionAddExpiresAtMs(builder, sanction.ExpiresAt.UTC().UnixMilli())
	}

	return userfbs.ActiveSanctionEnd(builder), nil
}

func decodeActiveSanction(sanction *userfbs.ActiveSanction) (usermodel.ActiveSanction, error) {
	actor := sanction.Actor(nil)
	if actor == nil {
		return usermodel.ActiveSanction{}, errors.New("sanction actor is missing")
	}

	decodedActor, err := decodeActorRef(actor)
	if err != nil {
		return usermodel.ActiveSanction{}, fmt.Errorf("decode sanction actor: %w", err)
	}

	return usermodel.ActiveSanction{
		SanctionCode: string(sanction.SanctionCode()),
		Scope:        string(sanction.Scope()),
		ReasonCode:   string(sanction.ReasonCode()),
		Actor:        decodedActor,
		AppliedAt:    time.UnixMilli(sanction.AppliedAtMs()).UTC(),
		ExpiresAt:    optionalUnixMilli(sanction.ExpiresAtMs()),
	}, nil
}

func encodeActiveLimit(builder *flatbuffers.Builder, limit usermodel.ActiveLimit) (flatbuffers.UOffsetT, error) {
	actorOffset := encodeActorRef(builder, limit.Actor)
	limitCode := builder.CreateString(limit.LimitCode)
	reasonCode := builder.CreateString(limit.ReasonCode)

	userfbs.ActiveLimitStart(builder)
	userfbs.ActiveLimitAddLimitCode(builder, limitCode)
	userfbs.ActiveLimitAddValue(builder, int64(limit.Value))
	userfbs.ActiveLimitAddReasonCode(builder, reasonCode)
	userfbs.ActiveLimitAddActor(builder, actorOffset)
	userfbs.ActiveLimitAddAppliedAtMs(builder, limit.AppliedAt.UTC().UnixMilli())
	if limit.ExpiresAt != nil {
		userfbs.ActiveLimitAddExpiresAtMs(builder, limit.ExpiresAt.UTC().UnixMilli())
	}

	return userfbs.ActiveLimitEnd(builder), nil
}

func decodeActiveLimit(limit *userfbs.ActiveLimit) (usermodel.ActiveLimit, error) {
	actor := limit.Actor(nil)
	if actor == nil {
		return usermodel.ActiveLimit{}, errors.New("limit actor is missing")
	}

	decodedActor, err := decodeActorRef(actor)
	if err != nil {
		return usermodel.ActiveLimit{}, fmt.Errorf("decode limit actor: %w", err)
	}

	value, err := int64ToInt(limit.Value(), "value")
	if err != nil {
		return usermodel.ActiveLimit{}, err
	}

	return usermodel.ActiveLimit{
		LimitCode:  string(limit.LimitCode()),
		Value:      value,
		ReasonCode: string(limit.ReasonCode()),
		Actor:      decodedActor,
		AppliedAt:  time.UnixMilli(limit.AppliedAtMs()).UTC(),
		ExpiresAt:  optionalUnixMilli(limit.ExpiresAtMs()),
	}, nil
}

func encodeActorRef(builder *flatbuffers.Builder, actor usermodel.ActorRef) flatbuffers.UOffsetT {
	actorType := builder.CreateString(actor.Type)

	var actorID flatbuffers.UOffsetT
	if actor.ID != "" {
		actorID = builder.CreateString(actor.ID)
	}

	userfbs.ActorRefStart(builder)
	userfbs.ActorRefAddType(builder, actorType)
	if actorID != 0 {
		userfbs.ActorRefAddId(builder, actorID)
	}

	return userfbs.ActorRefEnd(builder)
}

func decodeActorRef(actor *userfbs.ActorRef) (usermodel.ActorRef, error) {
	return usermodel.ActorRef{
		Type: string(actor.Type()),
		ID:   string(actor.Id()),
	}, nil
}

func encodeErrorBody(builder *flatbuffers.Builder, errorBody usermodel.ErrorBody) flatbuffers.UOffsetT {
	code := builder.CreateString(errorBody.Code)
	message := builder.CreateString(errorBody.Message)

	userfbs.ErrorBodyStart(builder)
	userfbs.ErrorBodyAddCode(builder, code)
	userfbs.ErrorBodyAddMessage(builder, message)

	return userfbs.ErrorBodyEnd(builder)
}

func optionalUnixMilli(value int64) *time.Time {
	if value == 0 {
		return nil
	}

	decoded := time.UnixMilli(value).UTC()
	return &decoded
}

func recoverUserDecodePanic[T any](message string, result **T, err *error) {
	if recovered := recover(); recovered != nil {
		*result = nil
		*err = fmt.Errorf("%s: panic recovered: %v", message, recovered)
	}
}

// ListMySessionsRequestToPayload converts usermodel.ListMySessionsRequest
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func ListMySessionsRequestToPayload(request *usermodel.ListMySessionsRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode list my sessions request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	userfbs.ListMySessionsRequestStart(builder)
	offset := userfbs.ListMySessionsRequestEnd(builder)
	userfbs.FinishListMySessionsRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToListMySessionsRequest converts FlatBuffers payload bytes into
// usermodel.ListMySessionsRequest.
func PayloadToListMySessionsRequest(data []byte) (result *usermodel.ListMySessionsRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode list my sessions request payload: data is empty")
	}

	defer recoverUserDecodePanic("decode list my sessions request payload", &result, &err)

	_ = userfbs.GetRootAsListMySessionsRequest(data, 0)
	return &usermodel.ListMySessionsRequest{}, nil
}

// ListMySessionsResponseToPayload converts usermodel.ListMySessionsResponse
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func ListMySessionsResponseToPayload(response *usermodel.ListMySessionsResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode list my sessions response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(256)
	itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items))
	for index := range response.Items {
		itemOffsets[index] = encodeDeviceSession(builder, response.Items[index])
	}

	var itemsVector flatbuffers.UOffsetT
	if len(itemOffsets) > 0 {
		userfbs.ListMySessionsResponseStartItemsVector(builder, len(itemOffsets))
		for index := len(itemOffsets) - 1; index >= 0; index-- {
			builder.PrependUOffsetT(itemOffsets[index])
		}
		itemsVector = builder.EndVector(len(itemOffsets))
	}

	userfbs.ListMySessionsResponseStart(builder)
	if itemsVector != 0 {
		userfbs.ListMySessionsResponseAddItems(builder, itemsVector)
	}
	offset := userfbs.ListMySessionsResponseEnd(builder)
	userfbs.FinishListMySessionsResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToListMySessionsResponse converts FlatBuffers payload bytes into
// usermodel.ListMySessionsResponse.
func PayloadToListMySessionsResponse(data []byte) (result *usermodel.ListMySessionsResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode list my sessions response payload: data is empty")
	}

	defer recoverUserDecodePanic("decode list my sessions response payload", &result, &err)

	response := userfbs.GetRootAsListMySessionsResponse(data, 0)
	count := response.ItemsLength()
	out := &usermodel.ListMySessionsResponse{
		Items: make([]usermodel.DeviceSession, 0, count),
	}
	view := new(userfbs.DeviceSessionView)
	for index := 0; index < count; index++ {
		if !response.Items(view, index) {
			return nil, fmt.Errorf("decode list my sessions response payload: item %d is missing", index)
		}
		out.Items = append(out.Items, decodeDeviceSession(view))
	}
	return out, nil
}

// RevokeMySessionRequestToPayload converts usermodel.RevokeMySessionRequest
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func RevokeMySessionRequestToPayload(request *usermodel.RevokeMySessionRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode revoke my session request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(64)
	deviceSessionID := builder.CreateString(request.DeviceSessionID)

	userfbs.RevokeMySessionRequestStart(builder)
	userfbs.RevokeMySessionRequestAddDeviceSessionId(builder, deviceSessionID)
	offset := userfbs.RevokeMySessionRequestEnd(builder)
	userfbs.FinishRevokeMySessionRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToRevokeMySessionRequest converts FlatBuffers payload bytes into
// usermodel.RevokeMySessionRequest.
func PayloadToRevokeMySessionRequest(data []byte) (result *usermodel.RevokeMySessionRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode revoke my session request payload: data is empty")
	}

	defer recoverUserDecodePanic("decode revoke my session request payload", &result, &err)

	request := userfbs.GetRootAsRevokeMySessionRequest(data, 0)
	return &usermodel.RevokeMySessionRequest{
		DeviceSessionID: string(request.DeviceSessionId()),
	}, nil
}

// RevokeMySessionResponseToPayload converts usermodel.RevokeMySessionResponse
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func RevokeMySessionResponseToPayload(response *usermodel.RevokeMySessionResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode revoke my session response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(128)
	sessionOffset := encodeDeviceSession(builder, response.Session)

	userfbs.RevokeMySessionResponseStart(builder)
	userfbs.RevokeMySessionResponseAddSession(builder, sessionOffset)
	offset := userfbs.RevokeMySessionResponseEnd(builder)
	userfbs.FinishRevokeMySessionResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToRevokeMySessionResponse converts FlatBuffers payload bytes into
// usermodel.RevokeMySessionResponse.
func PayloadToRevokeMySessionResponse(data []byte) (result *usermodel.RevokeMySessionResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode revoke my session response payload: data is empty")
	}

	defer recoverUserDecodePanic("decode revoke my session response payload", &result, &err)

	response := userfbs.GetRootAsRevokeMySessionResponse(data, 0)
	view := response.Session(nil)
	if view == nil {
		return nil, errors.New("decode revoke my session response payload: session is missing")
	}
	return &usermodel.RevokeMySessionResponse{Session: decodeDeviceSession(view)}, nil
}

// RevokeAllMySessionsRequestToPayload converts
// usermodel.RevokeAllMySessionsRequest to FlatBuffers bytes suitable for
// the authenticated gateway transport.
func RevokeAllMySessionsRequestToPayload(request *usermodel.RevokeAllMySessionsRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode revoke all my sessions request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	userfbs.RevokeAllMySessionsRequestStart(builder)
	offset := userfbs.RevokeAllMySessionsRequestEnd(builder)
	userfbs.FinishRevokeAllMySessionsRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToRevokeAllMySessionsRequest converts FlatBuffers payload bytes
// into usermodel.RevokeAllMySessionsRequest.
func PayloadToRevokeAllMySessionsRequest(data []byte) (result *usermodel.RevokeAllMySessionsRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode revoke all my sessions request payload: data is empty")
	}

	defer recoverUserDecodePanic("decode revoke all my sessions request payload", &result, &err)

	_ = userfbs.GetRootAsRevokeAllMySessionsRequest(data, 0)
	return &usermodel.RevokeAllMySessionsRequest{}, nil
}

// RevokeAllMySessionsResponseToPayload converts
// usermodel.RevokeAllMySessionsResponse to FlatBuffers bytes suitable
// for the authenticated gateway transport.
func RevokeAllMySessionsResponseToPayload(response *usermodel.RevokeAllMySessionsResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode revoke all my sessions response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(64)
	userID := builder.CreateString(response.Summary.UserID)

	userfbs.DeviceSessionRevocationSummaryViewStart(builder)
	userfbs.DeviceSessionRevocationSummaryViewAddUserId(builder, userID)
	userfbs.DeviceSessionRevocationSummaryViewAddRevokedCount(builder, int32(response.Summary.RevokedCount))
	summaryOffset := userfbs.DeviceSessionRevocationSummaryViewEnd(builder)

	userfbs.RevokeAllMySessionsResponseStart(builder)
	userfbs.RevokeAllMySessionsResponseAddSummary(builder, summaryOffset)
	offset := userfbs.RevokeAllMySessionsResponseEnd(builder)
	userfbs.FinishRevokeAllMySessionsResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToRevokeAllMySessionsResponse converts FlatBuffers payload bytes
// into usermodel.RevokeAllMySessionsResponse.
func PayloadToRevokeAllMySessionsResponse(data []byte) (result *usermodel.RevokeAllMySessionsResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode revoke all my sessions response payload: data is empty")
	}

	defer recoverUserDecodePanic("decode revoke all my sessions response payload", &result, &err)

	response := userfbs.GetRootAsRevokeAllMySessionsResponse(data, 0)
	summary := response.Summary(nil)
	if summary == nil {
		return nil, errors.New("decode revoke all my sessions response payload: summary is missing")
	}
	return &usermodel.RevokeAllMySessionsResponse{
		Summary: usermodel.DeviceSessionRevocationSummary{
			UserID:       string(summary.UserId()),
			RevokedCount: int(summary.RevokedCount()),
		},
	}, nil
}

func encodeDeviceSession(builder *flatbuffers.Builder, sess usermodel.DeviceSession) flatbuffers.UOffsetT {
	deviceSessionID := builder.CreateString(sess.DeviceSessionID)
	userID := builder.CreateString(sess.UserID)
	status := builder.CreateString(sess.Status)
	var clientPublicKey flatbuffers.UOffsetT
	if sess.ClientPublicKey != "" {
		clientPublicKey = builder.CreateString(sess.ClientPublicKey)
	}

	userfbs.DeviceSessionViewStart(builder)
	userfbs.DeviceSessionViewAddDeviceSessionId(builder, deviceSessionID)
	userfbs.DeviceSessionViewAddUserId(builder, userID)
	userfbs.DeviceSessionViewAddStatus(builder, status)
	if clientPublicKey != 0 {
		userfbs.DeviceSessionViewAddClientPublicKey(builder, clientPublicKey)
	}
	userfbs.DeviceSessionViewAddCreatedAtMs(builder, sess.CreatedAt.UTC().UnixMilli())
	if sess.RevokedAt != nil {
		userfbs.DeviceSessionViewAddRevokedAtMs(builder, sess.RevokedAt.UTC().UnixMilli())
	}
	if sess.LastSeenAt != nil {
		userfbs.DeviceSessionViewAddLastSeenAtMs(builder, sess.LastSeenAt.UTC().UnixMilli())
	}
	return userfbs.DeviceSessionViewEnd(builder)
}

func decodeDeviceSession(view *userfbs.DeviceSessionView) usermodel.DeviceSession {
	return usermodel.DeviceSession{
		DeviceSessionID: string(view.DeviceSessionId()),
		UserID:          string(view.UserId()),
		Status:          string(view.Status()),
		ClientPublicKey: string(view.ClientPublicKey()),
		CreatedAt:       time.UnixMilli(view.CreatedAtMs()).UTC(),
		RevokedAt:       optionalUnixMilli(view.RevokedAtMs()),
		LastSeenAt:      optionalUnixMilli(view.LastSeenAtMs()),
	}
}
