package controllers

import (
	"context"
	"crypto/rand"
	"fmt"
	"math/big"
	"time"

	"doormile/config"
	"doormile/db"
	"doormile/dto"
	"doormile/internal/mail"
	"doormile/utils"

	"github.com/gofiber/fiber/v2"
	"github.com/redis/go-redis/v9"
)

const (
	otpTTL         = 5 * time.Minute
	otpMaxAttempts = 5
)

func generateOtpCode() string {
	n, err := rand.Int(rand.Reader, big.NewInt(1000000))
	if err != nil {
		return "000000"
	}
	return fmt.Sprintf("%06d", n.Int64())
}

func otpKey(email string) string         { return fmt.Sprintf("otp:email:%s", email) }
func otpAttemptsKey(email string) string { return fmt.Sprintf("otp:email:%s:attempts", email) }

func SendCustomerEmailOtp(cfg *config.Config) fiber.Handler {
	return func(c *fiber.Ctx) error {
		req := new(dto.SendEmailOtpRequest)
		if err := c.BodyParser(req); err != nil {
			return utils.BadRequest(c, "invalid request body")
		}
		if req.Email == "" {
			return utils.BadRequest(c, "email is required")
		}

		if db.Rdb == nil {
			return utils.Internal(c, "verification service unavailable")
		}

		code := generateOtpCode()
		ctx := context.Background()

		if err := db.Rdb.Set(ctx, otpKey(req.Email), code, otpTTL).Err(); err != nil {
			return utils.Internal(c, "failed to generate verification code")
		}
		db.Rdb.Del(ctx, otpAttemptsKey(req.Email))

		if err := mail.SendOTPEmail(cfg, req.Email, code); err != nil {
			utils.Warn("failed to send OTP email", "email", req.Email, "error", err)
			return utils.Internal(c, "failed to send verification email")
		}

		return utils.Message(c, "verification code sent")
	}
}

func VerifyCustomerEmailOtp() fiber.Handler {
	return func(c *fiber.Ctx) error {
		req := new(dto.VerifyEmailOtpRequest)
		if err := c.BodyParser(req); err != nil {
			return utils.BadRequest(c, "invalid request body")
		}
		if req.Email == "" || req.Otp == "" {
			return utils.BadRequest(c, "email and otp are required")
		}

		if db.Rdb == nil {
			return utils.Internal(c, "verification service unavailable")
		}

		ctx := context.Background()
		key := otpKey(req.Email)

		stored, err := db.Rdb.Get(ctx, key).Result()
		if err == redis.Nil {
			return utils.BadRequest(c, "verification code expired or not found, please resend")
		} else if err != nil {
			return utils.Internal(c, "failed to verify code")
		}

		if stored != req.Otp {
			attemptsKey := otpAttemptsKey(req.Email)
			attempts, _ := db.Rdb.Incr(ctx, attemptsKey).Result()
			db.Rdb.Expire(ctx, attemptsKey, otpTTL)
			if attempts >= otpMaxAttempts {
				db.Rdb.Del(ctx, key, attemptsKey)
				return utils.BadRequest(c, "too many incorrect attempts, please request a new code")
			}
			return utils.Unauthorized(c, "incorrect verification code")
		}

		db.Rdb.Del(ctx, key, otpAttemptsKey(req.Email))
		return utils.Message(c, "email verified successfully")
	}
}
