package com.social.media.domain.company;

import com.social.media.domain.shared.BaseValueObject;
import lombok.EqualsAndHashCode;
import lombok.Getter;

import java.util.Objects;

/**
 * Value Object representing a postal address.
 * 
 * Encapsulates all address information including street, number, complement,
 * neighborhood, city, state, zip code, and country.
 * 
 * @author Social Media Manager Team
 * @version 2.0
 * @since 2025-01-01
 */
@Getter
@EqualsAndHashCode(callSuper = false)
public final class Address extends BaseValueObject {
    
    private final String street;
    private final String number;
    private final String complement;
    private final String neighborhood;
    private final String city;
    private final String state;
    private final String zipCode;
    private final String country;
    
    /**
     * Creates a new Address with all fields.
     * 
     * @param street the street name
     * @param number the street number
     * @param complement the address complement (apartment, suite, etc.)
     * @param neighborhood the neighborhood
     * @param city the city
     * @param state the state or province
     * @param zipCode the postal code
     * @param country the country
     */
    public Address(String street, String number, String complement, String neighborhood,
                   String city, String state, String zipCode, String country) {
        this.street = trimOrNull(street);
        this.number = trimOrNull(number);
        this.complement = trimOrNull(complement);
        this.neighborhood = trimOrNull(neighborhood);
        this.city = trimOrNull(city);
        this.state = trimOrNull(state);
        this.zipCode = normalizeZipCode(zipCode);
        this.country = trimOrNull(country) != null ? trimOrNull(country) : "Brasil";
        
        super.validate();
    }
    
    /**
     * Creates a new Address with required fields only.
     * 
     * @param street the street name
     * @param number the street number
     * @param city the city
     * @param state the state or province
     * @param zipCode the postal code
     */
    public Address(String street, String number, String city, String state, String zipCode) {
        this(street, number, null, null, city, state, zipCode, "Brasil");
    }
    
    @Override
    public void validate() {
        validateNotEmpty(street, "Street");
        validateNotEmpty(number, "Number");
        validateNotEmpty(city, "City");
        validateNotEmpty(state, "State");
        validateNotEmpty(zipCode, "Zip code");
        validateNotEmpty(country, "Country");
        
        // Validate zip code format for Brazil
        if ("Brasil".equalsIgnoreCase(country) && !isValidBrazilianZipCode(zipCode)) {
            throw new IllegalArgumentException("Invalid Brazilian zip code format");
        }
    }
    
    /**
     * Gets the complete address as a single formatted string.
     * 
     * @return formatted address string
     */
    public String getFormattedAddress() {
        StringBuilder formatted = new StringBuilder();
        
        // Street and number
        formatted.append(street).append(", ").append(number);
        
        // Complement
        if (complement != null && !complement.isBlank()) {
            formatted.append(", ").append(complement);
        }
        
        // Neighborhood
        if (neighborhood != null && !neighborhood.isBlank()) {
            formatted.append(", ").append(neighborhood);
        }
        
        // City and state
        formatted.append(", ").append(city).append(" - ").append(state);
        
        // Zip code
        formatted.append(", ").append(getFormattedZipCode());
        
        // Country (if not Brazil)
        if (!"Brasil".equalsIgnoreCase(country)) {
            formatted.append(", ").append(country);
        }
        
        return formatted.toString();
    }
    
    /**
     * Gets the zip code formatted according to the country.
     * 
     * @return formatted zip code
     */
    public String getFormattedZipCode() {
        if ("Brasil".equalsIgnoreCase(country) && zipCode != null && zipCode.length() == 8) {
            return zipCode.substring(0, 5) + "-" + zipCode.substring(5);
        }
        return zipCode;
    }
    
    /**
     * Gets the street address (street + number + complement).
     * 
     * @return street address string
     */
    public String getStreetAddress() {
        StringBuilder streetAddress = new StringBuilder();
        streetAddress.append(street).append(", ").append(number);
        
        if (complement != null && !complement.isBlank()) {
            streetAddress.append(", ").append(complement);
        }
        
        return streetAddress.toString();
    }
    
    /**
     * Checks if this address is in Brazil.
     * 
     * @return true if the address is in Brazil
     */
    public boolean isBrazilianAddress() {
        return "Brasil".equalsIgnoreCase(country) || "Brazil".equalsIgnoreCase(country);
    }
    
    /**
     * Checks if this address is complete (has all optional fields).
     * 
     * @return true if all fields are present
     */
    public boolean isComplete() {
        return complement != null && !complement.isBlank() &&
               neighborhood != null && !neighborhood.isBlank();
    }
    
    /**
     * Creates a new Address with updated complement.
     * 
     * @param newComplement the new complement
     * @return new Address instance
     */
    public Address withComplement(String newComplement) {
        return new Address(street, number, newComplement, neighborhood, 
                          city, state, zipCode, country);
    }
    
    /**
     * Creates a new Address with updated neighborhood.
     * 
     * @param newNeighborhood the new neighborhood
     * @return new Address instance
     */
    public Address withNeighborhood(String newNeighborhood) {
        return new Address(street, number, complement, newNeighborhood, 
                          city, state, zipCode, country);
    }
    
    /**
     * Validates if a zip code is in valid Brazilian format.
     * 
     * @param zipCode the zip code to validate
     * @return true if valid Brazilian zip code
     */
    private boolean isValidBrazilianZipCode(String zipCode) {
        return zipCode != null && zipCode.matches("\\d{8}");
    }
    
    /**
     * Normalizes a zip code by removing non-digit characters.
     * 
     * @param zipCode the zip code to normalize
     * @return normalized zip code
     */
    private String normalizeZipCode(String zipCode) {
        if (zipCode == null) {
            return null;
        }
        return zipCode.replaceAll("\\D", "");
    }
    
    /**
     * Trims a string and returns null if empty.
     * 
     * @param value the string to trim
     * @return trimmed string or null
     */
    private String trimOrNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Address address = (Address) obj;
        return Objects.equals(street, address.street) &&
               Objects.equals(number, address.number) &&
               Objects.equals(complement, address.complement) &&
               Objects.equals(neighborhood, address.neighborhood) &&
               Objects.equals(city, address.city) &&
               Objects.equals(state, address.state) &&
               Objects.equals(zipCode, address.zipCode) &&
               Objects.equals(country, address.country);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(street, number, complement, neighborhood, 
                           city, state, zipCode, country);
    }
    
    @Override
    public String toString() {
        return getFormattedAddress();
    }
}
