package com.social.media.domain.user;

import com.social.media.domain.shared.BaseValueObject;
import lombok.EqualsAndHashCode;
import lombok.Getter;

import java.util.Objects;
import java.util.regex.Pattern;

/**
 * Value Object representing a Brazilian CPF (Individual Taxpayer Registry).
 * 
 * CPF is a unique identifier for individuals in Brazil, similar to SSN in the US.
 * It must be exactly 11 digits and pass validation algorithm.
 * 
 * @author Social Media Manager Team
 * @version 2.0
 * @since 2025-01-01
 */
@Getter
@EqualsAndHashCode(callSuper = false)
public final class Cpf extends BaseValueObject {
    
    private static final Pattern CPF_PATTERN = Pattern.compile("\\d{11}");
    private static final int[] WEIGHT_1 = {10, 9, 8, 7, 6, 5, 4, 3, 2};
    private static final int[] WEIGHT_2 = {11, 10, 9, 8, 7, 6, 5, 4, 3, 2};
    
    private final String value;
    
    /**
     * Creates a new CPF from a string value.
     * 
     * @param value the CPF string (11 digits)
     * @throws IllegalArgumentException if the CPF is invalid
     */
    public Cpf(String value) {
        validateNotNull(value, "CPF");
        
        // Remove any non-digit characters
        String cleanValue = value.replaceAll("\\D", "");
        
        this.value = cleanValue;
        super.validate(); // This will call our validate() method
    }
    
    @Override
    public void validate() {
        // Check format (exactly 11 digits)
        if (!CPF_PATTERN.matcher(value).matches()) {
            throw new IllegalArgumentException("CPF must contain exactly 11 digits");
        }
        
        // Check for known invalid patterns (all same digits)
        if (value.matches("(\\d)\\1{10}")) {
            throw new IllegalArgumentException("CPF cannot have all identical digits");
        }
        
        // Validate check digits
        if (!isValidCheckDigits()) {
            throw new IllegalArgumentException("Invalid CPF check digits");
        }
    }
    
    /**
     * Gets the CPF value without formatting.
     * 
     * @return the raw CPF string
     */
    public String getValue() {
        return value;
    }
    
    /**
     * Gets the CPF value with standard formatting (XXX.XXX.XXX-XX).
     * 
     * @return the formatted CPF string
     */
    public String getFormatted() {
        return String.format("%s.%s.%s-%s",
                value.substring(0, 3),
                value.substring(3, 6),
                value.substring(6, 9),
                value.substring(9, 11)
        );
    }
    
    /**
     * Gets the region code (first digit) that indicates the region of issuance.
     * 
     * @return the region code
     */
    public int getRegionCode() {
        return Character.getNumericValue(value.charAt(8));
    }
    
    /**
     * Gets the region name based on the region code.
     * 
     * @return the region name
     */
    public String getRegionName() {
        return switch (getRegionCode()) {
            case 0 -> "Rio Grande do Sul";
            case 1 -> "Distrito Federal, Goiás, Mato Grosso, Mato Grosso do Sul, Tocantins";
            case 2 -> "Pará, Amazonas, Acre, Amapá, Rondônia, Roraima";
            case 3 -> "Ceará, Maranhão, Piauí";
            case 4 -> "Pernambuco, Rio Grande do Norte, Paraíba, Alagoas";
            case 5 -> "Bahia, Sergipe";
            case 6 -> "Minas Gerais";
            case 7 -> "Rio de Janeiro, Espírito Santo";
            case 8 -> "São Paulo";
            case 9 -> "Paraná, Santa Catarina";
            default -> "Unknown";
        };
    }
    
    /**
     * Gets the check digits (last 2 digits).
     * 
     * @return the check digits
     */
    public String getCheckDigits() {
        return value.substring(9, 11);
    }
    
    /**
     * Creates a masked version of the CPF for display purposes.
     * Example: 12345678901 becomes ***.***.***-01
     * 
     * @return masked CPF string
     */
    public String getMasked() {
        return "***.***." + value.substring(6, 9) + "-" + value.substring(9, 11);
    }
    
    /**
     * Creates a new CPF instance from a formatted string.
     * Automatically removes formatting characters.
     * 
     * @param formattedCpf the formatted CPF string
     * @return new CPF instance
     * @throws IllegalArgumentException if the CPF is invalid
     */
    public static Cpf fromFormatted(String formattedCpf) {
        return new Cpf(formattedCpf);
    }
    
    /**
     * Validates if a string represents a valid CPF.
     * 
     * @param cpfString the CPF string to validate
     * @return true if the CPF is valid
     */
    public static boolean isValid(String cpfString) {
        try {
            new Cpf(cpfString);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }
    
    /**
     * Creates a CPF instance if the string is valid, otherwise returns null.
     * 
     * @param cpfString the CPF string
     * @return CPF instance or null if invalid
     */
    public static Cpf of(String cpfString) {
        try {
            return new Cpf(cpfString);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }
    
    /**
     * Validates the check digits using the CPF algorithm.
     * 
     * @return true if check digits are valid
     */
    private boolean isValidCheckDigits() {
        // Calculate first check digit
        int sum1 = 0;
        for (int i = 0; i < 9; i++) {
            sum1 += Character.getNumericValue(value.charAt(i)) * WEIGHT_1[i];
        }
        int remainder1 = sum1 % 11;
        int checkDigit1 = remainder1 < 2 ? 0 : 11 - remainder1;
        
        // Calculate second check digit
        int sum2 = 0;
        for (int i = 0; i < 10; i++) {
            sum2 += Character.getNumericValue(value.charAt(i)) * WEIGHT_2[i];
        }
        int remainder2 = sum2 % 11;
        int checkDigit2 = remainder2 < 2 ? 0 : 11 - remainder2;
        
        // Verify both check digits
        return checkDigit1 == Character.getNumericValue(value.charAt(9)) &&
               checkDigit2 == Character.getNumericValue(value.charAt(10));
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Cpf cpf = (Cpf) obj;
        return Objects.equals(value, cpf.value);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
    
    @Override
    public String toString() {
        return getFormatted();
    }
}
