package com.social.media.domain.company;

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 CNPJ (Corporate Taxpayer Registry).
 * 
 * CNPJ is a unique identifier for companies in Brazil, similar to EIN in the US.
 * It must be exactly 14 digits and pass validation algorithm.
 * 
 * @author Social Media Manager Team
 * @version 2.0
 * @since 2025-01-01
 */
@Getter
@EqualsAndHashCode(callSuper = false)
public final class Cnpj extends BaseValueObject {
    
    private static final Pattern CNPJ_PATTERN = Pattern.compile("\\d{14}");
    private static final int[] WEIGHT_1 = {5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
    private static final int[] WEIGHT_2 = {6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2};
    
    private final String value;
    
    /**
     * Creates a new CNPJ from a string value.
     * 
     * @param value the CNPJ string (14 digits)
     * @throws IllegalArgumentException if the CNPJ is invalid
     */
    public Cnpj(String value) {
        validateNotNull(value, "CNPJ");
        
        // 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 14 digits)
        if (!CNPJ_PATTERN.matcher(value).matches()) {
            throw new IllegalArgumentException("CNPJ must contain exactly 14 digits");
        }
        
        // Check for known invalid patterns (all same digits)
        if (value.matches("(\\d)\\1{13}")) {
            throw new IllegalArgumentException("CNPJ cannot have all identical digits");
        }
        
        // Validate check digits
        if (!isValidCheckDigits()) {
            throw new IllegalArgumentException("Invalid CNPJ check digits");
        }
    }
    
    /**
     * Gets the CNPJ value without formatting.
     * 
     * @return the raw CNPJ string
     */
    public String getValue() {
        return value;
    }
    
    /**
     * Gets the CNPJ value with standard formatting (XX.XXX.XXX/XXXX-XX).
     * 
     * @return the formatted CNPJ string
     */
    public String getFormatted() {
        return String.format("%s.%s.%s/%s-%s",
                value.substring(0, 2),
                value.substring(2, 5),
                value.substring(5, 8),
                value.substring(8, 12),
                value.substring(12, 14)
        );
    }
    
    /**
     * Gets the company identifier part (first 8 digits).
     * 
     * @return the company identifier
     */
    public String getCompanyIdentifier() {
        return value.substring(0, 8);
    }
    
    /**
     * Gets the branch identifier part (digits 9-12).
     * 
     * @return the branch identifier
     */
    public String getBranchIdentifier() {
        return value.substring(8, 12);
    }
    
    /**
     * Gets the check digits (last 2 digits).
     * 
     * @return the check digits
     */
    public String getCheckDigits() {
        return value.substring(12, 14);
    }
    
    /**
     * Checks if this CNPJ represents the head office (branch 0001).
     * 
     * @return true if this is the head office
     */
    public boolean isHeadOffice() {
        return "0001".equals(getBranchIdentifier());
    }
    
    /**
     * Creates a new CNPJ instance from a formatted string.
     * Automatically removes formatting characters.
     * 
     * @param formattedCnpj the formatted CNPJ string
     * @return new CNPJ instance
     * @throws IllegalArgumentException if the CNPJ is invalid
     */
    public static Cnpj fromFormatted(String formattedCnpj) {
        return new Cnpj(formattedCnpj);
    }
    
    /**
     * Validates if a string represents a valid CNPJ.
     * 
     * @param cnpjString the CNPJ string to validate
     * @return true if the CNPJ is valid
     */
    public static boolean isValid(String cnpjString) {
        try {
            new Cnpj(cnpjString);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }
    
    /**
     * Validates the check digits using the CNPJ algorithm.
     * 
     * @return true if check digits are valid
     */
    private boolean isValidCheckDigits() {
        // Calculate first check digit
        int sum1 = 0;
        for (int i = 0; i < 12; 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 < 13; 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(12)) &&
               checkDigit2 == Character.getNumericValue(value.charAt(13));
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Cnpj cnpj = (Cnpj) obj;
        return Objects.equals(value, cnpj.value);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
    
    @Override
    public String toString() {
        return getFormatted();
    }
}
