"""
Base Value Object Abstract Class

Value objects are immutable objects defined by their attributes,
not by a unique identity.
"""

from abc import ABC
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class BaseValueObject(ABC):
    """
    Abstract base class for value objects.
    
    Value objects are defined by their attributes and are immutable.
    Two value objects are equal if all their attributes are equal.
    
    Rules:
    - No identity (no ID field)
    - Equality based on all values
    - Immutable (use frozen=True)
    - Internal validation in __post_init__
    - No ID or persistence-related fields
    """
    
    def __eq__(self, other: Any) -> bool:
        """Value objects are equal if all attributes are equal."""
        if not isinstance(other, self.__class__):
            return False
        return self.__dict__ == other.__dict__
    
    def __hash__(self) -> int:
        """Hash based on all attributes."""
        return hash(tuple(sorted(self.__dict__.items())))


@dataclass(frozen=True)
class Money(BaseValueObject):
    """
    Value object representing monetary amounts.
    
    Example of a properly implemented value object.
    """
    
    amount: float
    currency: str = "IRR"  # Iranian Rial default
    
    def __post_init__(self):
        """Validate on construction."""
        if self.amount < 0:
            raise ValueError("Amount cannot be negative")
        if len(self.currency) != 3:
            raise ValueError("Currency must be 3-letter ISO code")
    
    def add(self, other: "Money") -> "Money":
        """Add two monetary values."""
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        return Money(amount=self.amount + other.amount, currency=self.currency)
    
    def subtract(self, other: "Money") -> "Money":
        """Subtract monetary values."""
        if self.currency != other.currency:
            raise ValueError("Cannot subtract different currencies")
        return Money(amount=self.amount - other.amount, currency=self.currency)
    
    def multiply(self, factor: float) -> "Money":
        """Multiply by a factor."""
        return Money(amount=self.amount * factor, currency=self.currency)
    
    def __str__(self) -> str:
        return f"{self.amount:,.2f} {self.currency}"


@dataclass(frozen=True)
class Email(BaseValueObject):
    """
    Value object representing an email address.
    """
    
    value: str
    
    def __post_init__(self):
        """Validate email format."""
        import re
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(pattern, self.value):
            raise ValueError(f"Invalid email format: {self.value}")
    
    @property
    def domain(self) -> str:
        """Extract domain from email."""
        return self.value.split('@')[1]
    
    def __str__(self) -> str:
        return self.value


@dataclass(frozen=True)
class PhoneNumber(BaseValueObject):
    """
    Value object representing a phone number.
    """
    
    value: str
    country_code: str = "+98"  # Iran default
    
    def __post_init__(self):
        """Validate phone number."""
        import re
        # Remove spaces and dashes for validation
        clean = re.sub(r'[\s\-]', '', self.value)
        if not clean.isdigit():
            raise ValueError("Phone number must contain only digits")
        if len(clean) < 10 or len(clean) > 15:
            raise ValueError("Invalid phone number length")
    
    @property
    def formatted(self) -> str:
        """Return formatted phone number."""
        return f"{self.country_code} {self.value}"
    
    def __str__(self) -> str:
        return self.formatted


@dataclass(frozen=True)
class Address(BaseValueObject):
    """
    Value object representing a physical address.
    """
    
    street: str
    city: str
    province: str
    postal_code: str
    country: str = "ایران"
    
    def __post_init__(self):
        """Validate address fields."""
        if not self.street or not self.city:
            raise ValueError("Street and city are required")
    
    @property
    def full_address(self) -> str:
        """Return complete address string."""
        return f"{self.street}, {self.city}, {self.province}, {self.postal_code}, {self.country}"
    
    def __str__(self) -> str:
        return self.full_address
