package de.effigenix.infrastructure.security;

import de.effigenix.application.usermanagement.PasswordHasher;
import de.effigenix.domain.usermanagement.PasswordHash;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * BCrypt implementation of PasswordHasher interface.
 * Uses BCrypt with strength 12 for password hashing.
 *
 * BCrypt is a password hashing function designed by Niels Provos and David Mazières,
 * based on the Blowfish cipher. It includes a salt to protect against rainbow table attacks
 * and an adaptive function: over time, the iteration count can be increased to make it slower,
 * protecting against brute-force search attacks even with increasing computation power.
 *
 * Strength 12 provides a good balance between security and performance:
 * - ~250ms on modern hardware (acceptable for login)
 * - 2^12 = 4,096 iterations
 * - Resistant to brute-force attacks
 *
 * Infrastructure Layer → Implements Application Layer port
 */
@Component
public class BCryptPasswordHasher implements PasswordHasher {

    private static final int BCRYPT_STRENGTH = 12;
    private static final int MIN_PASSWORD_LENGTH = 8;

    private final BCryptPasswordEncoder encoder;

    public BCryptPasswordHasher() {
        this.encoder = new BCryptPasswordEncoder(BCRYPT_STRENGTH);
    }

    /**
     * Hashes a plain-text password using BCrypt with strength 12.
     *
     * @param plainPassword Plain-text password (never stored!)
     * @return BCrypt hash (60 characters, starts with $2a$, $2b$, or $2y$)
     * @throws IllegalArgumentException if password is invalid
     */
    @Override
    public PasswordHash hash(String plainPassword) {
        if (plainPassword == null || plainPassword.isBlank()) {
            throw new IllegalArgumentException("Password cannot be null or empty");
        }
        if (!isValidPassword(plainPassword)) {
            throw new IllegalArgumentException("Password does not meet minimum requirements");
        }

        String bcryptHash = encoder.encode(plainPassword);
        return PasswordHash.of(bcryptHash);
    }

    /**
     * Verifies a plain-text password against a BCrypt hash.
     * Uses constant-time comparison to prevent timing attacks.
     *
     * @param plainPassword Plain-text password to verify
     * @param passwordHash BCrypt hash to compare against
     * @return true if password matches, false otherwise
     */
    @Override
    public boolean verify(String plainPassword, PasswordHash passwordHash) {
        if (plainPassword == null || passwordHash == null) {
            return false;
        }

        try {
            return encoder.matches(plainPassword, passwordHash.value());
        } catch (Exception e) {
            // Invalid hash format or other error - return false instead of throwing
            return false;
        }
    }

    /**
     * Validates password strength (minimum requirements).
     *
     * Requirements:
     * - Minimum 8 characters
     * - At least one uppercase letter
     * - At least one lowercase letter
     * - At least one digit
     * - At least one special character
     *
     * @param plainPassword Plain-text password to validate
     * @return true if password meets requirements, false otherwise
     */
    @Override
    public boolean isValidPassword(String plainPassword) {
        if (plainPassword == null || plainPassword.length() < MIN_PASSWORD_LENGTH) {
            return false;
        }

        boolean hasUpperCase = false;
        boolean hasLowerCase = false;
        boolean hasDigit = false;
        boolean hasSpecialChar = false;

        for (char c : plainPassword.toCharArray()) {
            if (Character.isUpperCase(c)) {
                hasUpperCase = true;
            } else if (Character.isLowerCase(c)) {
                hasLowerCase = true;
            } else if (Character.isDigit(c)) {
                hasDigit = true;
            } else if (!Character.isWhitespace(c)) {
                hasSpecialChar = true;
            }
        }

        return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar;
    }
}
