package resolvers

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"

	"github.com/authorizerdev/authorizer/server/constants"
	"github.com/authorizerdev/authorizer/server/cookie"
	"github.com/authorizerdev/authorizer/server/db"
	"github.com/authorizerdev/authorizer/server/db/models"
	mailService "github.com/authorizerdev/authorizer/server/email"
	"github.com/authorizerdev/authorizer/server/graph/model"
	"github.com/authorizerdev/authorizer/server/memorystore"
	"github.com/authorizerdev/authorizer/server/refs"
	"github.com/authorizerdev/authorizer/server/smsproviders"
	"github.com/authorizerdev/authorizer/server/utils"
)

// ResendOTPResolver is a resolver for resend otp mutation
func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) {
	email := strings.ToLower(strings.Trim(refs.StringValue(params.Email), " "))
	phoneNumber := strings.Trim(refs.StringValue(params.PhoneNumber), " ")
	log := log.WithFields(log.Fields{
		"email":        email,
		"phone_number": phoneNumber,
	})
	if email == "" && phoneNumber == "" {
		log.Debug("Email or phone number is required")
		return nil, errors.New("email or phone number is required")
	}
	gc, err := utils.GinContextFromContext(ctx)
	if err != nil {
		log.Debug("Failed to get GinContext: ", err)
		return nil, err
	}
	var user *models.User
	var isEmailServiceEnabled, isSMSServiceEnabled bool
	if email != "" {
		isEmailServiceEnabled, err = memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
		if err != nil || !isEmailServiceEnabled {
			log.Debug("Email service not enabled: ", err)
			return nil, errors.New("email service not enabled")
		}
		user, err = db.Provider.GetUserByEmail(ctx, email)
		if err != nil {
			log.Debug("Failed to get user by email: ", err)
			return nil, fmt.Errorf(`user with this email/phone not found`)
		}
	} else {
		isSMSServiceEnabled, err = memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
		if err != nil || !isSMSServiceEnabled {
			log.Debug("Email service not enabled: ", err)
			return nil, errors.New("email service not enabled")
		}
		user, err = db.Provider.GetUserByPhoneNumber(ctx, phoneNumber)
		if err != nil {
			log.Debug("Failed to get user by phone: ", err)
			return nil, fmt.Errorf(`user with this email/phone not found`)
		}
	}
	if user.RevokedTimestamp != nil {
		log.Debug("User access is revoked")
		return nil, fmt.Errorf(`user access has been revoked`)
	}

	if !refs.BoolValue(user.IsMultiFactorAuthEnabled) && user.EmailVerifiedAt != nil && user.PhoneNumberVerifiedAt != nil {
		log.Debug("User multi factor authentication is not enabled")
		return nil, fmt.Errorf(`multi factor authentication not enabled`)
	}

	isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
	if err != nil || isMFADisabled {
		log.Debug("MFA service not enabled: ", err)
		return nil, errors.New("multi factor authentication is disabled for this instance")
	}

	// get otp by email or phone number
	var otpData *models.OTP
	if email != "" {
		otpData, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email))
	} else {
		otpData, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber))
	}
	if err != nil {
		log.Debug("Failed to get otp for given email: ", err)
		return nil, err
	}
	if otpData == nil {
		log.Debug("No otp found for given email: ", params.Email)
		return &model.Response{
			Message: "Failed to get for given email",
		}, errors.New("failed to get otp for given email")
	}
	// If multi factor authentication is enabled and we need to generate OTP for mail / sms based MFA
	generateOTP := func(expiresAt int64) (*models.OTP, error) {
		otp := utils.GenerateOTP()
		otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{
			Email:       refs.StringValue(user.Email),
			PhoneNumber: refs.StringValue(user.PhoneNumber),
			Otp:         otp,
			ExpiresAt:   expiresAt,
		})
		if err != nil {
			log.Debug("Failed to add otp: ", err)
			return nil, err
		}
		return otpData, nil
	}
	setOTPMFaSession := func(expiresAt int64) error {
		mfaSession := uuid.NewString()
		err = memorystore.Provider.SetMfaSession(user.ID, mfaSession, expiresAt)
		if err != nil {
			log.Debug("Failed to add mfasession: ", err)
			return err
		}
		cookie.SetMfaSession(gc, mfaSession)
		return nil
	}
	expiresAt := time.Now().Add(1 * time.Minute).Unix()
	otpData, err = generateOTP(expiresAt)
	if err != nil {
		log.Debug("Failed to generate otp: ", err)
		return nil, err
	}
	if err := setOTPMFaSession(expiresAt); err != nil {
		log.Debug("Failed to set mfa session: ", err)
		return nil, err
	}
	if email != "" {
		go func() {
			// exec it as go routine so that we can reduce the api latency
			if err := mailService.SendEmail([]string{email}, constants.VerificationTypeOTP, map[string]interface{}{
				"user":         user.ToMap(),
				"organization": utils.GetOrganization(),
				"otp":          otpData.Otp,
			}); err != nil {
				log.Debug("Failed to send otp email: ", err)
			}
			utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
		}()
	} else {
		go func() {
			smsBody := strings.Builder{}
			smsBody.WriteString("Your verification code is: ")
			smsBody.WriteString(otpData.Otp)
			utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, user)
			if err := smsproviders.SendSMS(phoneNumber, smsBody.String()); err != nil {
				log.Debug("Failed to send sms: ", err)
			}
		}()
	}
	log.Info("OTP has been resent")
	return &model.Response{
		Message: `OTP has been sent. Please check your inbox`,
	}, nil
}
