package de.assecutor.votianlt.pages.service;

import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.repository.AppUserRepository;
import de.assecutor.votianlt.repository.UserRepository;
import de.assecutor.votianlt.service.EmailService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.security.SecureRandom;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HexFormat;
import java.util.Optional;

@Service
public class PasswordResetService {

    public enum UserType {
        USERS, APP_USER
    }

    private final UserRepository userRepository;
    private final AppUserRepository appUserRepository;
    private final PasswordEncoder passwordEncoder;
    private final EmailService emailService;
    private static final Duration TOKEN_VALIDITY = Duration.ofMinutes(15);

    public PasswordResetService(UserRepository userRepository, AppUserRepository appUserRepository,
            PasswordEncoder passwordEncoder, EmailService emailService) {
        this.userRepository = userRepository;
        this.appUserRepository = appUserRepository;
        this.passwordEncoder = passwordEncoder;
        this.emailService = emailService;
    }

    /**
     * Initiate reset without asking for user type. Looks up the email in both
     * collections and only proceeds if it exists in exactly one of them. Otherwise,
     * it silently returns to avoid leaking account existence.
     */
    public void initiateResetAuto(String email, String baseUrl) {
        if (email == null)
            return;
        String normalized = email.trim();
        if (normalized.isEmpty())
            return;
        var userOpt = userRepository.findByEmail(normalized);
        var appUser = appUserRepository.findByEmail(normalized);
        boolean inUsers = userOpt.isPresent();
        boolean inAppUsers = appUser != null;
        if (inUsers == inAppUsers) {
            // Either in both or in none: do nothing to keep behavior indistinguishable
            return;
        }
        UserType type = inUsers ? UserType.USERS : UserType.APP_USER;
        initiateResetInternal(normalized, type, baseUrl);
    }

    private void initiateResetInternal(String email, UserType userType, String baseUrl) {
        String token = generateToken64();
        LocalDateTime now = LocalDateTime.now();
        String typeParam = userType == UserType.USERS ? "users" : "app_user";
        String link = baseUrl + "/forget-password?token=" + token + "&type=" + typeParam;

        switch (userType) {
        case USERS -> {
            Optional<User> optional = userRepository.findByEmail(email);
            if (optional.isEmpty()) {
                // Do not leak existence; simply return
                return;
            }
            User user = optional.get();
            user.setPasswordCode(token);
            user.setPasswordTimestamp(now);
            userRepository.save(user);
            sendMail(email, link);
        }
        case APP_USER -> {
            AppUser appUser = appUserRepository.findByEmail(email);
            if (appUser == null) {
                return;
            }
            appUser.setPasswordCode(token);
            appUser.setPasswordTimestamp(now);
            appUserRepository.save(appUser);
            sendMail(email, link);
        }
        }
    }

    private void sendMail(String to, String link) {
        String subject = "Passwort zurücksetzen";
        String body = "Hallo,\n\n" + "Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt. "
                + "Dieser Link ist 15 Minuten gültig:\n" + link + "\n\n"
                + "Wenn Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren.";
        try {
            emailService.sendSimpleEmail(to, subject, body);
        } catch (Exception e) {
            // In this minimal implementation we swallow to avoid leaking details to
            // attackers
        }
    }

    public boolean isTokenValid(String token, UserType userType) {
        LocalDateTime ts = switch (userType) {
        case USERS -> userRepository.findByPasswordCode(token).map(User::getPasswordTimestamp).orElse(null);
        case APP_USER -> Optional.ofNullable(appUserRepository.findByPasswordCode(token))
                .map(AppUser::getPasswordTimestamp).orElse(null);
        };
        if (ts == null)
            return false;
        return Duration.between(ts, LocalDateTime.now()).compareTo(TOKEN_VALIDITY) <= 0;
    }

    public boolean resetPassword(String token, UserType userType, String newPassword) {
        if (!isTokenValid(token, userType))
            return false;
        switch (userType) {
        case USERS -> {
            Optional<User> optional = userRepository.findByPasswordCode(token);
            if (optional.isEmpty())
                return false;
            User user = optional.get();
            user.setPassword(passwordEncoder.encode(newPassword));
            user.setPasswordCode(null);
            user.setPasswordTimestamp(null);
            userRepository.save(user);
            return true;
        }
        case APP_USER -> {
            AppUser appUser = appUserRepository.findByPasswordCode(token);
            if (appUser == null)
                return false;
            appUser.setPassword(passwordEncoder.encode(newPassword));
            appUser.setPasswordCode(null);
            appUser.setPasswordTimestamp(null);
            appUserRepository.save(appUser);
            return true;
        }
        }
        return false;
    }

    private String generateToken64() {
        // 32 bytes -> 64 hex characters
        byte[] bytes = new byte[32];
        new SecureRandom().nextBytes(bytes);
        return HexFormat.of().formatHex(bytes);
    }
}
