# app/utils/money.py
"""
Money handling utilities using integer cents.

All monetary values are stored as integers representing cents.
This eliminates floating-point precision issues common in financial applications.

Example:
    €105.91 is stored as 10591 (integer cents)

Usage:
    from app.utils.money import euros_to_cents, cents_to_euros, Money

    # Convert euros to cents for storage
    price_cents = euros_to_cents(105.91)  # Returns 10591

    # Convert cents to euros for display
    price_euros = cents_to_euros(10591)  # Returns 105.91

    # Format for display
    formatted = Money.format(10591)  # Returns "105.91"
    formatted = Money.format(10591, "EUR")  # Returns "105.91 EUR"

    # Parse price string from CSV/API
    cents = parse_price_to_cents("19.99 EUR")  # Returns 1999

See docs/architecture/money-handling.md for full documentation.
"""

import re
from decimal import ROUND_HALF_UP, Decimal

# Type alias for clarity
Cents = int
Euros = float

# Currency configurations
CURRENCY_DECIMALS = {
    "EUR": 2,
    "USD": 2,
    "GBP": 2,
    "CHF": 2,
    "JPY": 0,  # Japanese Yen has no decimal places
}

DEFAULT_CURRENCY = "EUR"


def euros_to_cents(euros: float | str | Decimal | int | None) -> Cents:
    """
    Convert a euro amount to cents.

    Uses Decimal internally for precise conversion, avoiding floating-point errors.

    Args:
        euros: Amount in euros (float, string, Decimal, int, or None)

    Returns:
        Amount in cents as integer

    Examples:
        >>> euros_to_cents(105.91)
        10591
        >>> euros_to_cents("19.99")
        1999
        >>> euros_to_cents(None)
        0
    """
    if euros is None:
        return 0

    if isinstance(euros, int):
        # Assume already an integer euro amount (rare)
        return euros * 100

    if isinstance(euros, str):
        # Clean the string: remove currency symbols, spaces, handle comma decimals
        cleaned = euros.strip()
        cleaned = re.sub(r"[€$£\s]", "", cleaned)
        cleaned = cleaned.replace(",", ".")
        euros = cleaned

    # Use Decimal for precise conversion
    if isinstance(euros, Decimal):
        d = euros
    else:
        # Convert via string to avoid float precision issues
        d = Decimal(str(euros))

    # Multiply by 100 and round to nearest integer
    cents = d * Decimal("100")
    return int(cents.quantize(Decimal("1"), rounding=ROUND_HALF_UP))


def cents_to_euros(cents: int | None) -> Euros:
    """
    Convert cents to euros.

    Args:
        cents: Amount in cents (integer or None)

    Returns:
        Amount in euros as float, rounded to 2 decimal places

    Examples:
        >>> cents_to_euros(10591)
        105.91
        >>> cents_to_euros(None)
        0.0
    """
    if cents is None:
        return 0.0

    # Simple integer division, then convert to float
    # Using Decimal to ensure exact representation
    euros = Decimal(cents) / Decimal("100")
    return float(euros.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))


def parse_price_to_cents(
    price_str: str | float | int | None,
    default_currency: str = DEFAULT_CURRENCY,
) -> tuple[Cents, str]:
    """
    Parse a price string and return cents and currency.

    Handles various formats:
    - "19.99 EUR"
    - "19,99 €"
    - "19.99"
    - 19.99 (float)

    Args:
        price_str: Price string or numeric value
        default_currency: Currency to use if not specified in string

    Returns:
        Tuple of (cents, currency_code)

    Examples:
        >>> parse_price_to_cents("19.99 EUR")
        (1999, "EUR")
        >>> parse_price_to_cents("19,99 €")
        (1999, "EUR")
        >>> parse_price_to_cents(19.99)
        (1999, "EUR")
    """
    if price_str is None:
        return (0, default_currency)

    if isinstance(price_str, int | float):
        return (euros_to_cents(price_str), default_currency)

    # Parse string
    price_str = str(price_str).strip()

    # Extract currency if present
    currency = default_currency
    currency_patterns = [
        (r"\bEUR\b", "EUR"),
        (r"€", "EUR"),
        (r"\bUSD\b", "USD"),
        (r"\$", "USD"),
        (r"\bGBP\b", "GBP"),
        (r"£", "GBP"),
        (r"\bCHF\b", "CHF"),
    ]

    for pattern, curr in currency_patterns:
        if re.search(pattern, price_str, re.IGNORECASE):
            currency = curr
            break

    # Remove currency symbols and extract numeric part
    numeric_str = re.sub(r"[€$£a-zA-Z\s]", "", price_str)

    # Handle European decimal comma
    numeric_str = numeric_str.replace(",", ".")

    # Handle thousand separators (e.g., "1.000,00" -> "1000.00")
    # If there are multiple dots, assume the last one is decimal
    parts = numeric_str.split(".")
    if len(parts) > 2:
        # Multiple dots: join all but last as thousands, last as decimal
        numeric_str = "".join(parts[:-1]) + "." + parts[-1]

    try:
        cents = euros_to_cents(numeric_str)
    except (ValueError, TypeError):
        cents = 0

    return (cents, currency)


class Money:
    """
    Money utility class for formatting and operations.

    This class provides static methods for money operations.
    All internal values are in cents (integers).
    """

    @staticmethod
    def from_euros(euros: float | str | Decimal | None) -> Cents:
        """Create cents from euro amount."""
        return euros_to_cents(euros)

    @staticmethod
    def from_cents(cents: int | None) -> Cents:
        """Passthrough for cents (for consistency)."""
        return cents if cents is not None else 0

    @staticmethod
    def to_euros(cents: int | None) -> Euros:
        """Convert cents to euros."""
        return cents_to_euros(cents)

    @staticmethod
    def format(
        cents: int | None,
        currency: str = "",
        locale: str = "en",
    ) -> str:
        """
        Format cents as a currency string.

        Args:
            cents: Amount in cents
            currency: Currency code to append (optional)
            locale: Locale for formatting (en, de, fr)

        Returns:
            Formatted price string

        Examples:
            >>> Money.format(10591)
            "105.91"
            >>> Money.format(10591, "EUR")
            "105.91 EUR"
            >>> Money.format(10591, "EUR", "de")
            "105,91 EUR"
        """
        if cents is None:
            cents = 0

        euros = cents_to_euros(cents)

        # Format based on locale
        if locale in ("de", "fr", "lb"):
            # European format: comma decimal, dot thousands
            formatted = f"{euros:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
        else:
            # English format: dot decimal, comma thousands
            formatted = f"{euros:,.2f}"

        if currency:
            return f"{formatted} {currency}"
        return formatted

    @staticmethod
    def parse(price_str: str | float | int | None) -> Cents:
        """
        Parse a price value to cents.

        Args:
            price_str: Price string or numeric value

        Returns:
            Amount in cents
        """
        cents, _ = parse_price_to_cents(price_str)
        return cents

    @staticmethod
    def add(*amounts: int | None) -> Cents:
        """
        Add multiple cent amounts.

        Args:
            *amounts: Cent amounts to add

        Returns:
            Sum in cents
        """
        return sum(a for a in amounts if a is not None)

    @staticmethod
    def subtract(amount: int, *deductions: int | None) -> Cents:
        """
        Subtract amounts from a base amount.

        Args:
            amount: Base amount in cents
            *deductions: Amounts to subtract

        Returns:
            Result in cents
        """
        result = amount
        for d in deductions:
            if d is not None:
                result -= d
        return result

    @staticmethod
    def multiply(cents: int, quantity: int) -> Cents:
        """
        Multiply cents by quantity.

        Args:
            cents: Unit price in cents
            quantity: Number of units

        Returns:
            Total in cents
        """
        return cents * quantity

    @staticmethod
    def calculate_line_total(unit_price_cents: int, quantity: int) -> Cents:
        """
        Calculate line total for an order item.

        Args:
            unit_price_cents: Price per unit in cents
            quantity: Number of units

        Returns:
            Line total in cents
        """
        return unit_price_cents * quantity

    @staticmethod
    def calculate_order_total(
        subtotal_cents: int,
        tax_cents: int = 0,
        shipping_cents: int = 0,
        discount_cents: int = 0,
    ) -> Cents:
        """
        Calculate order total.

        Args:
            subtotal_cents: Sum of line items in cents
            tax_cents: Tax amount in cents
            shipping_cents: Shipping cost in cents
            discount_cents: Discount amount in cents

        Returns:
            Total in cents
        """
        return subtotal_cents + tax_cents + shipping_cents - discount_cents


# Convenience exports
__all__ = [
    "euros_to_cents",
    "cents_to_euros",
    "parse_price_to_cents",
    "Money",
    "Cents",
    "Euros",
]
