package transcode

import (
	"context"

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

// Account linking & merge message types. The email ops carry the costly-
// email rate flag; the telegram ops validate Login Widget data through the
// connector (registered only when the connector is configured). All are
// authenticated. The merge ops are the explicit irreversible step, gated in the UI
// after a merge_required confirm.
const (
	MsgLinkEmailRequest   = "link.email.request"
	MsgLinkEmailConfirm   = "link.email.confirm"
	MsgLinkEmailMerge     = "link.email.merge"
	MsgLinkTelegram       = "link.telegram.confirm"
	MsgLinkTelegramMerge  = "link.telegram.merge"
	MsgLinkVK             = "link.vk.confirm"
	MsgLinkVKMerge        = "link.vk.merge"
	MsgLinkUnlink         = "link.unlink"
	MsgEmailChangeRequest = "link.email.change.request"
	MsgEmailChangeConfirm = "link.email.change.confirm"
	MsgAccountDeleteReq   = "account.delete.request"
	MsgAccountDeleteConf  = "account.delete.confirm"
)

// registerLinkOps adds the linking & merge operations. The telegram ops need the
// connector's Login Widget validator, so they are registered only when tg is set.
func registerLinkOps(r *Registry, backend *backendclient.Client, tg TelegramValidator) {
	r.ops[MsgLinkEmailRequest] = Op{Handler: linkEmailRequestHandler(backend), Auth: true, Email: true}
	r.ops[MsgLinkEmailConfirm] = Op{Handler: linkEmailConfirmHandler(backend), Auth: true, Email: true}
	r.ops[MsgLinkEmailMerge] = Op{Handler: linkEmailMergeHandler(backend), Auth: true, Email: true}
	r.ops[MsgLinkUnlink] = Op{Handler: linkUnlinkHandler(backend), Auth: true}
	r.ops[MsgEmailChangeRequest] = Op{Handler: changeEmailRequestHandler(backend), Auth: true, Email: true}
	r.ops[MsgEmailChangeConfirm] = Op{Handler: changeEmailConfirmHandler(backend), Auth: true, Email: true}
	r.ops[MsgAccountDeleteReq] = Op{Handler: deleteRequestHandler(backend), Auth: true, Email: true}
	r.ops[MsgAccountDeleteConf] = Op{Handler: deleteConfirmHandler(backend), Auth: true}
	if tg != nil {
		r.ops[MsgLinkTelegram] = Op{Handler: linkTelegramHandler(backend, tg, false), Auth: true}
		r.ops[MsgLinkTelegramMerge] = Op{Handler: linkTelegramHandler(backend, tg, true), Auth: true}
	}
}

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

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

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

// changeEmailRequestHandler mails a confirm-code to a new address for an email change.
func changeEmailRequestHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsLinkEmailRequest(req.Payload, 0)
		if err := backend.ChangeEmailRequest(ctx, req.UserID, string(in.Email())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

// changeEmailConfirmHandler verifies the code and switches the account's email, returning
// the refreshed link result (status "changed" + the updated profile).
func changeEmailConfirmHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsLinkEmailConfirm(req.Payload, 0)
		res, err := backend.ChangeEmailConfirm(ctx, req.UserID, string(in.Email()), string(in.Code()))
		if err != nil {
			return nil, err
		}
		return encodeLinkResult(res), nil
	}
}

// deleteRequestHandler starts account deletion, returning which step-up the account uses.
func deleteRequestHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		res, err := backend.DeleteRequest(ctx, req.UserID)
		if err != nil {
			return nil, err
		}
		return encodeDeleteRequestResult(res.Method), nil
	}
}

// deleteConfirmHandler verifies the step-up proof and deletes the account.
func deleteConfirmHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsAccountDeleteConfirm(req.Payload, 0)
		if err := backend.DeleteConfirm(ctx, req.UserID, string(in.Code()), string(in.Phrase())); err != nil {
			return nil, err
		}
		return encodeAck(true), nil
	}
}

// linkUnlinkHandler detaches a platform identity (telegram|vk) from the caller and
// returns the refreshed link result (status "unlinked" + the updated profile).
func linkUnlinkHandler(backend *backendclient.Client) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsLinkUnlinkRequest(req.Payload, 0)
		res, err := backend.LinkUnlink(ctx, req.UserID, string(in.Kind()))
		if err != nil {
			return nil, err
		}
		return encodeLinkResult(res), nil
	}
}

// linkTelegramHandler validates Login Widget data via the connector and then calls
// the backend's link or merge endpoint with the trusted Telegram external id.
func linkTelegramHandler(backend *backendclient.Client, tg TelegramValidator, merge bool) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsLinkTelegramRequest(req.Payload, 0)
		user, err := tg.ValidateLoginWidget(ctx, string(in.Data()))
		if err != nil {
			return nil, err
		}
		var res backendclient.LinkResultResp
		if merge {
			res, err = backend.LinkTelegramMerge(ctx, req.UserID, user.ExternalID)
		} else {
			res, err = backend.LinkTelegram(ctx, req.UserID, user.ExternalID)
		}
		if err != nil {
			return nil, err
		}
		return encodeLinkResult(res), nil
	}
}

// VKIDExchanger completes a VK ID web authorization-code exchange, returning the
// launching user's trusted VK identity. It is satisfied by *vkid.Exchanger and lets the
// VK web-link ops resolve a browser VK login, which — unlike the Mini App path — has no
// offline launch signature to verify.
type VKIDExchanger interface {
	Exchange(ctx context.Context, code, deviceID, codeVerifier string) (vkid.Identity, error)
}

// registerVKLinkOps adds the VK web-link ops when a VK ID exchanger is configured; a nil
// exchanger leaves them unregistered (VK ID web login not configured).
func registerVKLinkOps(r *Registry, backend *backendclient.Client, ex VKIDExchanger) {
	if ex == nil {
		return
	}
	r.ops[MsgLinkVK] = Op{Handler: linkVKHandler(backend, ex, false), Auth: true}
	r.ops[MsgLinkVKMerge] = Op{Handler: linkVKHandler(backend, ex, true), Auth: true}
}

// linkVKHandler completes the VK ID code exchange (server-side, under the app's
// protected key) and then calls the backend's link or merge endpoint with the trusted
// VK external id.
func linkVKHandler(backend *backendclient.Client, ex VKIDExchanger, merge bool) Handler {
	return func(ctx context.Context, req Request) ([]byte, error) {
		in := fb.GetRootAsLinkVKRequest(req.Payload, 0)
		user, err := ex.Exchange(ctx, string(in.Code()), string(in.DeviceId()), string(in.CodeVerifier()))
		if err != nil {
			return nil, err
		}
		var res backendclient.LinkResultResp
		if merge {
			res, err = backend.LinkVKMerge(ctx, req.UserID, user.ExternalID)
		} else {
			res, err = backend.LinkVK(ctx, req.UserID, user.ExternalID)
		}
		if err != nil {
			return nil, err
		}
		return encodeLinkResult(res), nil
	}
}
