package com.hepl.tunefortwo.service.impl;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import com.hepl.tunefortwo.service.MailService;
import com.hepl.tunefortwo.service.OtpService;
import com.hepl.tunefortwo.service.TemplateService;

import jakarta.mail.MessagingException;

@Service
public class OtpServiceImpl implements OtpService {

    private static final Logger logger = LoggerFactory.getLogger(OtpServiceImpl.class);
    private final JavaMailSender mailSender;
    private final MailService mailService;
    private final TemplateService templateService;
    private final RedisTemplate<String, String> redisTemplate;

    @Autowired
    public OtpServiceImpl(JavaMailSender mailSender, MailService mailService, TemplateService templateService, RedisTemplate<String, String> redisTemplate) {
        this.mailSender = mailSender;
        this.mailService = mailService;
        this.templateService = templateService;
        this.redisTemplate = redisTemplate;
    }

    private static final long OTP_VALID_DURATION = 10 * 60 * 1000; // 10 minutes
    private static final long RESEND_OTP_DELAY = 1 * 60 * 1000; // 1 minute
    private static final int MAX_RESEND_ATTEMPTS = 5;
    private static final int MAX_REQUESTS_PER_WINDOW = 50; // Max requests per time window
    private static final long THROTTLE_WINDOW_DURATION = 1 * 60 * 60 * 1000; // 1 hour
    private static final String LAST_SENT_KEY_SUFFIX = ":lastSent";
    private static final String RESEND_COUNT_KEY_SUFFIX = ":resendCount";
    private static final String REQUEST_COUNT_KEY_SUFFIX = ":requestCount";

    @Override
    public void sendOtp(String email, String name) throws MessagingException {
        // Check throttling based on email
        if (isThrottled(email)) {
            throw new IllegalStateException("Too many requests. Please try again later.");
        }

        String otp = generateOtp();
        logger.info("Generated OTP for {}: {}", email, otp);

        saveOtpToRedis(email, otp);
        updateOtpMetaData(email, true);

        mailService.sendMailByTemplate(templateService.getOtpTemplate(name, otp), email, "OTP Email");
    }

    @Override
    public boolean verifyOtp(String email, String otp) {
        String cachedOtp = redisTemplate.opsForValue().get(email);
        if (cachedOtp != null && cachedOtp.equals(otp)) {
            redisTemplate.delete(email);
            redisTemplate.delete(email + LAST_SENT_KEY_SUFFIX);
            redisTemplate.delete(email + RESEND_COUNT_KEY_SUFFIX);

            String verifiedKey = email + ":verified";
            redisTemplate.opsForValue().set(verifiedKey, "true", OTP_VALID_DURATION, TimeUnit.MILLISECONDS);
            return true;
        }
        return false;
    }

    @Override
    public String generateOtp() {
        return String.format("%06d", new Random().nextInt(999999));
    }

    @Override
    public void resendOtp(String email, String name) throws MessagingException {
        String lastSentKey = email + LAST_SENT_KEY_SUFFIX;
        String resendCountKey = email + RESEND_COUNT_KEY_SUFFIX;

        Long lastSentTime = redisTemplate.opsForValue().get(lastSentKey) != null ?
                Long.parseLong(redisTemplate.opsForValue().get(lastSentKey)) : 0;
        Integer resendCount = redisTemplate.opsForValue().get(resendCountKey) != null ?
                Integer.parseInt(redisTemplate.opsForValue().get(resendCountKey)) : 0;

        long currentTime = System.currentTimeMillis();

        if (currentTime - lastSentTime < RESEND_OTP_DELAY) {
            throw new IllegalStateException("Please wait before requesting a new OTP.");
        }
        if (resendCount >= MAX_RESEND_ATTEMPTS) {
            throw new IllegalStateException("Too many resend attempts. Please try again later.");
        }

        String otp = generateOtp();
        saveOtpToRedis(email, otp);
        updateOtpMetaData(email, false);

        logger.info("Resent OTP for {}: {}", email, otp);
        sendOtp(email, name);
    }

    @Override
    public void saveOtpToRedis(String email, String otp) {
        redisTemplate.opsForValue().set(email, otp, OTP_VALID_DURATION, TimeUnit.MILLISECONDS);
    }

    @Override
    public void updateOtpMetaData(String email, boolean isFirstSend) {
        String lastSentKey = email + LAST_SENT_KEY_SUFFIX;
        String resendCountKey = email + RESEND_COUNT_KEY_SUFFIX;

        long currentTime = System.currentTimeMillis();
        redisTemplate.opsForValue().set(lastSentKey, String.valueOf(currentTime), OTP_VALID_DURATION, TimeUnit.MILLISECONDS);
        if (isFirstSend) {
            redisTemplate.opsForValue().set(resendCountKey, "0", OTP_VALID_DURATION, TimeUnit.MILLISECONDS);
        } else {
            redisTemplate.opsForValue().increment(resendCountKey);
        }
    }

    private boolean isThrottled(String email) {
        String requestCountKey = email + REQUEST_COUNT_KEY_SUFFIX;
        Long requestCount = redisTemplate.opsForValue().get(requestCountKey) != null ?
                Long.parseLong(redisTemplate.opsForValue().get(requestCountKey)) : 0;
        Long currentTime = System.currentTimeMillis();
        Long windowStart = currentTime - THROTTLE_WINDOW_DURATION;

        if (requestCount >= MAX_REQUESTS_PER_WINDOW) {
            return true;
        }

        redisTemplate.opsForValue().increment(requestCountKey);
        redisTemplate.expire(requestCountKey, THROTTLE_WINDOW_DURATION, TimeUnit.MILLISECONDS);

        return false;
    }

    @Override
    public boolean isEmailVerified(String email) {
        String verifiedKey = email + ":verified";
        return "true".equals(redisTemplate.opsForValue().get(verifiedKey));
    }
}
