package auth

import (
	"backend/internal/domain"
	"backend/internal/utils"
	"backend/internal/utils/email_template"
	errs "backend/pkg/library/errors"
	"backend/pkg/member/domain/member"
	"backend/pkg/member/domain/usecase"
	notificationUC "backend/pkg/notification/domain/usecase"
	"bytes"
	"context"
	"fmt"
	"html/template"

	"backend/internal/svc"
	"backend/internal/types"

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

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

func NewRequestPasswordResetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RequestPasswordResetLogic {
	return &RequestPasswordResetLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

// RequestPasswordReset 請求發送密碼重設驗證碼 aka 忘記密碼
func (l *RequestPasswordResetLogic) RequestPasswordReset(req *types.RequestPasswordResetReq) (resp *types.RespOK, err error) {
	// 驗證並標準化帳號
	acc, err := l.validateAndNormalizeAccount(req.AccountType, req.Identifier)
	if err != nil {
		return nil, err
	}

	// 檢查發送冷卻時間
	rk := domain.GenerateVerifyCodeRedisKey.With(fmt.Sprintf("%s:%d", acc, member.GenerateCodeTypeForgetPassword)).ToString()
	if err := l.checkVerifyCodeCooldown(rk); err != nil {
		return nil, err
	}

	// 確認帳號是否註冊並檢查平台限制
	if err := l.checkAccountAndPlatform(acc); err != nil {
		return nil, err
	}

	// 生成驗證碼
	vcode, err := l.svcCtx.AccountUC.GenerateRefreshCode(l.ctx, usecase.GenerateRefreshCodeRequest{
		LoginID:  acc,
		CodeType: member.GenerateCodeTypeForgetPassword,
	})
	if err != nil {
		return nil, err
	}

	// 獲取用戶資訊並確認綁定帳號
	account, err := l.svcCtx.AccountUC.GetUIDByAccount(l.ctx, usecase.GetUIDByAccountRequest{Account: acc})
	if err != nil {
		return nil, errs.ResNotFoundError(fmt.Sprintf("account not found:%s", acc))
	}
	info, err := l.svcCtx.AccountUC.GetUserInfo(l.ctx, usecase.GetUserInfoRequest{UID: account.UID})
	if err != nil {
		return nil, err
	}

	nickname := generateMsgName(&info)
	switch member.GetAccountTypeByCode(req.AccountType) {
	case member.AccountTypeMail:
		body, title, err := email_template.GetEmailTemplate(email_template.Language(info.PreferredLanguage), email_template.ForgetPasswordVerify)
		if err != nil {
			e := errs.ResNotFoundError("failed to get correct email template")
			return nil, e
		}

		tmpl, err := template.New("ForgetPasswordEmail").Parse(body)
		if err != nil {
			e := errs.ResInvalidFormatError("failed to get correct email template")
			return nil, e
		}

		emailParams := email_template.ForgetPasswordEmailReq{
			Username:   nickname,
			VerifyCode: vcode.Data.VerifyCode,
		}

		var buf bytes.Buffer
		if err := tmpl.Execute(&buf, emailParams); err != nil {
			e := errs.ResInvalidFormatError("failed to build data")
			return nil, e
		}

		err = l.svcCtx.DeliveryUC.SendEmail(l.ctx, notificationUC.MailReq{
			To:         []string{req.Identifier},
			From:       l.svcCtx.Config.SMTPConfig.Sender,
			SenderName: l.svcCtx.Config.SMTPConfig.SenderName,
			Subject:    title,
			Body:       buf.String(),
		})
		if err != nil {
			e := errs.SvcThirdPartyError("failed to send email").Wrap(err)

			return nil, e
		}
	case member.AccountTypePhone:
		//// 送出手機號碼
		//templateResp, err := l.svcCtx.NotificationUseCase.GetSMSTemplateByTypeID(
		//	l.ctx, notificationModule.Language(info.PreferredLanguage), notificationModule.BindingPhone)
		//if err != nil {
		//	return nil, err
		//}
		//
		//fmt.Println(fmt.Sprintf("%s:%s", templateResp.Body, vcode.Data.VerifyCode))
		////err = l.svcCtx.NotificationUseCase.SendMessage(l.ctx, notificationModule.SMSMessageRequest{
		////	PhoneNumber:    acc,
		////	RecipientName:  nickname,
		////	MessageContent: fmt.Sprintf("%s:%s", templateResp.Body, vcode),
		////})
		////if err != nil {
		////	return nil, err
		////}
	case member.AccountTypeNone:
	case member.AccountTypeDefine:
	default:
		return &types.RespOK{}, errs.InputInvalidRangeError("")
	}

	// 設置 Redis 鍵
	l.setRedisKeyWithExpiry(rk, vcode.Data.VerifyCode, 60)

	return &types.RespOK{}, nil
}

// validateAndNormalizeAccount 驗證並標準化帳號
func (l *RequestPasswordResetLogic) validateAndNormalizeAccount(accountType, account string) (string, error) {
	switch member.GetAccountTypeByCode(accountType) {
	case member.AccountTypePhone:
		phone, isPhone := utils.NormalizeTaiwanMobile(account)
		if !isPhone {
			return "", errs.InputInvalidFormatError("phone number is invalid")
		}

		return phone, nil
	case member.AccountTypeMail:
		if !utils.IsValidEmail(account) {
			return "", errs.InputInvalidFormatError("email is invalid")
		}

		return account, nil
	case member.AccountTypeNone, member.AccountTypeDefine:
	default:
	}

	return "", errs.InputInvalidFormatError("unsupported account type")
}

// checkVerifyCodeCooldown 檢查是否已在限制時間內發送過驗證碼
func (l *RequestPasswordResetLogic) checkVerifyCodeCooldown(rk string) error {
	if cachedCode, err := l.svcCtx.Redis.GetCtx(l.ctx, rk); err != nil || cachedCode != "" {
		return errs.SysTooManyRequestError("verification code already sent, please wait 3min for system to send again")
	}

	return nil
}

// checkAccountAndPlatform 檢查帳號是否註冊及平台限制
func (l *RequestPasswordResetLogic) checkAccountAndPlatform(acc string) error {
	accountInfo, err := l.svcCtx.AccountUC.GetUserAccountInfo(l.ctx, usecase.GetUIDByAccountRequest{Account: acc})
	if err != nil {
		return err
	}

	if accountInfo.Data.Platform != member.Digimon {
		return errs.InputInvalidFormatError(
			"failed to send verify code since platform not correct")
	}

	return nil
}

// setRedisKeyWithExpiry 設置 Redis 鍵
func (l *RequestPasswordResetLogic) setRedisKeyWithExpiry(rk, verifyCode string, expiry int) {
	if status, err := l.svcCtx.Redis.SetnxExCtx(l.ctx, rk, verifyCode, expiry); err != nil || !status {
		_ = errs.DBErrorErrorL(l.svcCtx.Logger, []errs.LogField{
			{Key: "redisKey", Val: rk},
			{Key: "error", Val: err.Error()},
		}, "failed to set redis expire").Wrap(err)
	}
}

// generateMsgName 取得寄信用的名稱
func generateMsgName(info *usecase.UserInfo) string {
	if info.FullName != nil {
		return *info.FullName
	}
	if info.Nickname != nil {
		return *info.Nickname
	}

	return info.UID
}
