package ru.soune.nocopy.handler.validator;

import org.apache.commons.validator.routines.EmailValidator;
import org.apache.commons.validator.routines.InetAddressValidator;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import ru.soune.nocopy.dto.register.RegRequest;

import java.util.*;

@Component
public class RegRequestValidator implements Validator {
    private static final String COMPANY_REGEX = "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'()\"\"«»„‟”“”‘'’‹›❛❜❝❞〝〞〟]{1,200}$";

    private static final String NAME_REGEX = "^[a-zA-Zа-яА-ЯёЁ\\s\\-'.]{2,100}$";

    @Override
    public boolean supports(Class<?> clazz) {
        return RegRequest.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        RegRequest request = (RegRequest) target;

        validatePhone(request.getPhone(), errors);
        validatePassword(request.getPassword(), errors);
        validateEmail(request.getEmail(), errors);
        validateCompanyName(request.getCompanyName(), errors);
        validateFullName(request.getFullName(), errors);
        validateAdditionalInfo(request.getIp(), request.getUserAgent(), errors);
    }

    private void validateFullName(String fullName, Errors errors) {
        if (fullName == null || fullName.trim().isEmpty()) {
            errors.rejectValue("fullName", "fullName.required",
                    "Full name is required");
            return;
        }

        String trimmedName = fullName.trim();

        if (trimmedName.length() < 2) {
            errors.rejectValue("fullName", "fullName.too.short",
                    "Full name must be at least 2 characters");
        }

        if (trimmedName.length() > 100) {
            errors.rejectValue("fullName", "fullName.too.long",
                    "Full name must be less than 100 characters");
        }

        if (!trimmedName.matches(NAME_REGEX)) {
            errors.rejectValue("fullName", "fullName.invalid.chars",
                    "Name can only contain letters, spaces, hyphens and apostrophes");
        }
    }

    public void validateCompanyName(String companyName, Errors errors) {
        if (companyName == null || companyName.trim().isEmpty()) {
            return;
        }

        String trimmedCompany = companyName.trim();

        if (trimmedCompany.length() > 200) {
            errors.rejectValue("companyName", "companyName.too.long",
                    "Company name must be less than 200 characters");
        }

        if (!trimmedCompany.matches(COMPANY_REGEX)) {
            errors.rejectValue("companyName", "companyName.invalid.chars",
                    "Company name contains invalid characters");
        }
    }

    public void validateAdditionalInfo(String ip, String userAgent, Errors errors) {
        if (ip == null || ip.trim().isEmpty()) {
            errors.rejectValue("ip", "ip.required", "IP address is required");
        } else if (!InetAddressValidator.getInstance().isValid(ip.trim())) {
            errors.rejectValue("ip", "ip.invalid", "Invalid IP address format");
        }

        if (userAgent == null || userAgent.trim().isEmpty()) {
            errors.rejectValue("userAgent", "userAgent.required", "User agent is required");
        } else if (userAgent.length() > 500) {
            errors.rejectValue("userAgent", "userAgent.tooLong", "User agent must not exceed " +
                    "500 characters");
        }
    }

    public void validateEmail(String email, Errors errors) {
        if (email == null || email.trim().isEmpty()) {
            errors.rejectValue("email", "email.is.empty", "Email must not be empty");
            return;
        }

        String trimmedEmail = email.trim().toLowerCase();

        if (trimmedEmail.length() > 254) {
            errors.rejectValue("email", "email.too.long", "Email is too long");
            return;
        }

        EmailValidator emailValidator = EmailValidator.getInstance(true, true);
        if (!emailValidator.isValid(trimmedEmail)) {
            errors.rejectValue("email", "email.invalid", "Invalid email address");
            return;
        }
    }


    public void validatePhone(String phone, Errors errors) {
        if (phone == null || phone.trim().isEmpty()) {
            errors.rejectValue("phone", "phone.empty",
                    "Test phone numbers are not allowed");
            return;
        }

        String trimmedPhone = phone.trim();
        String digitsOnly = trimmedPhone.replaceAll("[^0-9+]", "");

        if (digitsOnly.length() < 11 || digitsOnly.length() > 14) {
            errors.rejectValue("phone", "phone.invalid.length",
                    "Phone number must contain 11-14 digits");
            return;
        }

        if (!digitsOnly.matches("^(\\+7|8|7)[0-9]{10}$")) {
            errors.rejectValue("phone", "phone.invalid.format",
                    "Invalid phone number format");
            return;
        }
    }

    private void validatePassword(String password, Errors errors) {
        if (password == null || password.isEmpty()) {
            errors.rejectValue("password", "password.empty",
                    "Password are not allowed");
            return;
        }

        if (password.length() < 8) {
            errors.rejectValue("password", "password.too.short",
                    "Password must be at least 8 characters");
        }

        if (password.length() > 50) {
            errors.rejectValue("password", "password.too.long",
                    "Password must be less than 50 characters");
        }

        if (password.contains(" ")) {
            errors.rejectValue("password", "password.contains.spaces",
                    "Password cannot contain spaces");
        }
    }

    private void checkPasswordComplexity(String password, Errors errors) {
        boolean hasUpper = false;
        boolean hasLower = false;
        boolean hasDigit = false;
        boolean hasSpecial = false;

        for (char c : password.toCharArray()) {
            if (Character.isUpperCase(c)) hasUpper = true;
            if (Character.isLowerCase(c)) hasLower = true;
            if (Character.isDigit(c)) hasDigit = true;
            if ("!@#$%^&*()_+-=[]{}|;:,.<>?".indexOf(c) >= 0) hasSpecial = true;
        }

        List<String> missing = new ArrayList<>();
        if (!hasUpper) missing.add("uppercase letter");
        if (!hasLower) missing.add("lowercase letter");
        if (!hasDigit) missing.add("digit");
        if (!hasSpecial) missing.add("special character");

        if (!missing.isEmpty()) {
            String message = "Password must contain: " + String.join(", ", missing);
            errors.rejectValue("password", "password.complexity.missing", message);
        }
    }

    private void checkCommonPasswords(String password, Errors errors) {
        Set<String> commonPasswords = Set.of(
                "password", "12345678", "qwerty123", "admin123",
                "welcome1", "password1", "123456789", "qwertyuiop"
        );

        if (commonPasswords.contains(password.toLowerCase())) {
            errors.rejectValue("password", "password.too.common",
                    "This password is too common, please choose another");
        }
    }

    private void checkForSequences(String password, Errors errors) {
        String lower = password.toLowerCase();

        if (lower.matches(".*123456.*") || lower.matches(".*987654.*")) {
            errors.rejectValue("password", "password.sequence.numbers",
                    "Password contains predictable number sequence");
        }

        if (lower.matches(".*abcdef.*") || lower.matches(".*qwerty.*")) {
            errors.rejectValue("password", "password.sequence.letters",
                    "Password contains predictable letter sequence");
        }

        if (lower.matches(".*(.)\\1{3,}.*")) {
            errors.rejectValue("password", "password.repeating.chars",
                    "Password contains too many repeating characters");
        }
    }
}
