package auth

import (
	"backend/internal/domain"
	"backend/internal/svc"
	"backend/internal/types"
	errs "backend/pkg/library/errors"
	mb "backend/pkg/member/domain/member"
	member "backend/pkg/member/domain/usecase"
	"backend/pkg/permission/domain/usecase"
	"context"

	"google.golang.org/protobuf/proto"

	"github.com/zeromicro/go-zero/core/logx"
)

type RegisterLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

var PrepareFunc map[string]func(ctx context.Context, req *types.LoginReq, svc *svc.ServiceContext) (buildData, error) = map[string]func(ctx context.Context, req *types.LoginReq, svc *svc.ServiceContext) (buildData, error){
	mb.Digimon.ToString(): buildCredentialsData,
	mb.Google.ToString():  buildGoogleData,
	mb.Line.ToString():    buildLineData,
}

// NewRegisterLogic 註冊新帳號
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
	return &RegisterLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *RegisterLogic) Register(req *types.LoginReq) (resp *types.LoginResp, err error) {
	// Step 1: 根據平台構建相關資料
	var bd buildData
	switch req.AuthMethod {
	case "credentials":
		fn, ok := PrepareFunc[mb.Digimon.ToString()]
		if !ok {
			return nil, errs.InputInvalidRangeError("failed to get correct credentials method")
		}
		bd, err = fn(l.ctx, req, l.svcCtx)
		if err != nil {
			return nil, err
		}
	case "platform":
		fn, ok := PrepareFunc[req.Platform.Provider]
		if !ok {
			return nil, errs.InputInvalidRangeError("failed to get correct credentials method")
		}
		bd, err = fn(l.ctx, req, l.svcCtx)
		if err != nil {
			return nil, err
		}
	default:
		return nil, errs.InputInvalidFormatError("failed to get correct auth method")
	}

	// Step 2: 建立帳號
	if err := l.svcCtx.AccountUC.CreateUserAccount(l.ctx, bd.CreateAccountReq); err != nil {
		return nil, err
	}
	// Step 3: 綁定帳號並獲取 UID
	account, err := l.svcCtx.AccountUC.BindAccount(l.ctx, bd.BindingUserReq)
	if err != nil {
		return nil, err
	}

	bd.CreateUserInfoRequest.UID = account.UID
	// Step 4: 更新使用者資訊
	if err := l.svcCtx.AccountUC.BindUserInfo(l.ctx, bd.CreateUserInfoRequest); err != nil {
		return nil, err
	}

	_, err = l.svcCtx.UserRoleUC.Assign(l.ctx, usecase.AssignRoleRequest{
		RoleUID: l.svcCtx.Config.RoleConfig.DefaultRoleName,
		UserUID: account.UID,
		Brand:   domain.DefaultBrand,
	})
	if err != nil {
		return nil, err
	}

	// Step 5: 生成 Token
	req.LoginID = bd.CreateAccountReq.LoginID
	tk, err := generateToken(l.svcCtx, l.ctx, req, account.UID, l.svcCtx.Config.RoleConfig.DefaultRoleName)
	if err != nil {
		return nil, err
	}

	return &types.LoginResp{
		UID:          account.UID,
		AccessToken:  tk.AccessToken,
		RefreshToken: tk.RefreshToken,
		TokenType:    tk.TokenType,
	}, nil
}

type buildData struct {
	CreateAccountReq      member.CreateLoginUserRequest
	BindingUserReq        member.BindingUser
	CreateUserInfoRequest member.CreateUserInfoRequest
}

func buildCredentialsData(_ context.Context, req *types.LoginReq, _ *svc.ServiceContext) (buildData, error) {
	return buildData{
		CreateAccountReq: member.CreateLoginUserRequest{
			LoginID:  req.LoginID,
			Token:    req.Credentials.Password,
			Platform: mb.Digimon,
		},
		BindingUserReq: member.BindingUser{
			LoginID: req.LoginID,
			Type:    mb.GetAccountTypeByCode(req.Credentials.AccountType),
		},
		CreateUserInfoRequest: member.CreateUserInfoRequest{
			AlarmCategory:     mb.AlarmNoAlert,
			UserStatus:        mb.AccountStatusUnverified,
			PreferredLanguage: mb.LangTW.ToString(),
			Currency:          mb.CurrencyTWD.ToString(),
		},
	}, nil
}

func buildGoogleData(ctx context.Context, req *types.LoginReq, svc *svc.ServiceContext) (buildData, error) {
	googleToken, err := svc.AccountUC.VerifyGoogleAuthResult(ctx, member.VerifyAuthResultRequest{
		Account: req.LoginID,
		Token:   req.Platform.Token,
	})
	if err != nil {
		return buildData{}, err
	}

	return buildData{
		CreateAccountReq: member.CreateLoginUserRequest{
			LoginID:  googleToken.Email,
			Token:    "",
			Platform: mb.Google,
		},
		BindingUserReq: member.BindingUser{
			LoginID: req.LoginID,
			Type:    mb.AccountTypeNone,
		},
		CreateUserInfoRequest: member.CreateUserInfoRequest{
			AvatarURL:         proto.String(googleToken.Picture),
			FullName:          proto.String(googleToken.Name),
			Nickname:          proto.String(googleToken.Name),
			Email:             proto.String(googleToken.Email),
			AlarmCategory:     mb.AlarmNoAlert,
			UserStatus:        mb.AccountStatusUnverified,
			PreferredLanguage: mb.LangTW.ToString(),
			Currency:          mb.CurrencyTWD.ToString(),
		},
	}, nil
}

func buildLineData(ctx context.Context, req *types.LoginReq, svc *svc.ServiceContext) (buildData, error) {
	lineAccessToken, err := svc.AccountUC.LineCodeToAccessToken(ctx, req.LoginID)
	if err != nil {
		return buildData{}, err
	}

	userInfo, err := svc.AccountUC.LineGetProfileByAccessToken(ctx, lineAccessToken.AccessToken)
	if err != nil {
		return buildData{}, err
	}
	return buildData{
		CreateAccountReq: member.CreateLoginUserRequest{
			LoginID:  userInfo.UserID,
			Token:    "",
			Platform: mb.Line,
		},
		BindingUserReq: member.BindingUser{
			LoginID: userInfo.UserID,
			Type:    mb.AccountTypeNone,
		},
		CreateUserInfoRequest: member.CreateUserInfoRequest{
			AvatarURL:         proto.String(userInfo.PictureURL),
			FullName:          proto.String(userInfo.DisplayName),
			Nickname:          proto.String(userInfo.DisplayName),
			AlarmCategory:     mb.AlarmNoAlert,
			UserStatus:        mb.AccountStatusUnverified,
			PreferredLanguage: mb.LangTW.ToString(),
			Currency:          mb.CurrencyTWD.ToString(),
		},
	}, nil
}
