// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package users

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"time"

	"github.com/absmach/supermq/pkg/errors"
	svcerr "github.com/absmach/supermq/pkg/errors/service"
)

const VerificationExpiryDuration = 24 * time.Hour

var (
	errFailedToCreateUserVerification = errors.New("failed to create new user verification")
	errFailedToEncodeUserVerification = errors.New("failed to encode user verification")
	errFailedToDecodeUserVerification = errors.New("failed to decode user verification")
)

// UserVerification OTP is sent to the user's email as base64 encoded with UserID, Email and OTP. It should not be exposed via API.
type UserVerification struct {
	UserID    string    `json:"user_id"`
	Email     string    `json:"email"`
	OTP       string    `json:"otp"`
	CreatedAt time.Time `json:"-"`
	ExpiresAt time.Time `json:"-"`
	UsedAt    time.Time `json:"-"`
}

func NewUserVerification(userID, email string) (UserVerification, error) {
	randomBytes := make([]byte, 32)
	if _, err := rand.Read(randomBytes); err != nil {
		return UserVerification{}, errors.Wrap(errFailedToCreateUserVerification, err)
	}

	return UserVerification{
		UserID:    userID,
		Email:     email,
		OTP:       base64.URLEncoding.EncodeToString(randomBytes),
		CreatedAt: time.Now().UTC(),
		ExpiresAt: time.Now().Add(VerificationExpiryDuration).UTC(),
	}, nil
}

func (u UserVerification) Encode() (string, error) {
	jsonBytes, err := json.Marshal(u)
	if err != nil {
		return "", errors.Wrap(errFailedToEncodeUserVerification, err)
	}

	return base64.URLEncoding.EncodeToString(jsonBytes), nil
}

func (u *UserVerification) Decode(data string) error {
	decodedPayload, err := base64.URLEncoding.DecodeString(data)
	if err != nil {
		return errors.Wrap(errFailedToDecodeUserVerification, err)
	}

	if err := json.Unmarshal(decodedPayload, u); err != nil {
		return errors.Wrap(errFailedToDecodeUserVerification, err)
	}

	if u.UserID == "" || u.Email == "" || u.OTP == "" {
		return svcerr.ErrInvalidUserVerification
	}

	return nil
}

func (u UserVerification) Valid() error {
	if u.UserID == "" || u.Email == "" || u.OTP == "" {
		return svcerr.ErrInvalidUserVerification
	}

	// Verification should have created time.
	if u.CreatedAt.IsZero() {
		return svcerr.ErrInvalidUserVerification
	}

	// Verification should have expiry time.
	if u.ExpiresAt.IsZero() {
		return svcerr.ErrInvalidUserVerification
	}

	// Expiry time should not be before Created time
	if u.ExpiresAt.Before(u.CreatedAt) {
		return svcerr.ErrInvalidUserVerification
	}

	// Verification should be not be Expired.
	if time.Now().After(u.ExpiresAt) {
		return svcerr.ErrUserVerificationExpired
	}

	// Verification should not be used.
	if !u.UsedAt.IsZero() {
		return svcerr.ErrUserVerificationExpired
	}

	return nil
}

func (u UserVerification) Match(ruv UserVerification) error {
	if u.UserID != ruv.UserID {
		return svcerr.ErrInvalidUserVerification
	}

	if u.Email != ruv.Email {
		return svcerr.ErrInvalidUserVerification
	}

	if u.OTP != ruv.OTP {
		return svcerr.ErrInvalidUserVerification
	}
	return nil
}
