"""
USPS Address Normalization Engine
Applies all USPS standardization rules to parsed address components.
"""

import re
from typing import Dict
from .constants import STREET_SUFFIXES, DIRECTIONALS, SECONDARY_UNITS, STATE_CODES
from .parser import parse_address_components, has_po_box
from .models import USPSAddress


def normalize_address(address: str) -> USPSAddress:
    """
    Normalize a US address to USPS standard format.

    Applies all USPS Publication 28 rules:
    - ALL CAPS
    - Standard abbreviations for streets, directionals, units, states
    - Removes periods and extra spaces
    - Separates into proper USPS lines

    Args:
        address: Raw address string in any format

    Returns:
        USPSAddress object with normalized components

    Example:
        >>> addr = normalize_address("128 king street floor 3 suite 301 san francisco california 94107")
        >>> print(addr.line1)
        128 KING ST
        >>> print(addr.line2)
        FL 3 STE 301
        >>> print(addr.city)
        SAN FRANCISCO
        >>> print(addr.state)
        CA
        >>> print(addr.zip_code)
        94107
    """
    # Parse into components
    components = parse_address_components(address)

    # Normalize each component
    line1 = _build_line1(components)
    line2 = _build_line2(components)
    city = _normalize_city(components)
    state = _normalize_state(components)
    zip_code = _normalize_zip(components)

    return USPSAddress(
        line1=line1,
        line2=line2,
        city=city,
        state=state,
        zip_code=zip_code
    )


def _build_line1(components: Dict[str, str]) -> str:
    """
    Build primary delivery address line (Line 1).

    Format: [Number] [PreDir] [Street Name] [Suffix] [PostDir]
    Example: 128 N KING ST

    Args:
        components: Parsed address components

    Returns:
        Normalized Line 1 (primary street address)
    """
    # Check for PO Box
    if has_po_box(components):
        po_box = components.get('po_box', '')
        return _clean_and_caps(po_box)

    parts = []

    # House number
    if 'house_number' in components:
        parts.append(components['house_number'])

    # Road (includes street name and may include directionals)
    if 'road' in components:
        road = components['road']
        road = _normalize_road(road)
        parts.append(road)

    # Combine and clean
    line1 = ' '.join(parts)
    return _clean_and_caps(line1)


def _build_line2(components: Dict[str, str]) -> str:
    """
    Build secondary address line (Line 2).

    Format: [Unit Type] [Unit Number]
    Examples:
        - FL 3 STE 301
        - APT 5B
        - (empty string if no secondary unit)

    Args:
        components: Parsed address components

    Returns:
        Normalized Line 2 (secondary addressing) or empty string
    """
    parts = []

    # Level (floor)
    if 'level' in components:
        level = _normalize_secondary_unit(components['level'])
        parts.append(level)

    # Unit (suite, apartment, etc.)
    if 'unit' in components:
        unit = _normalize_secondary_unit(components['unit'])
        parts.append(unit)

    if not parts:
        return ''

    line2 = ' '.join(parts)
    return _clean_and_caps(line2)


def _normalize_road(road: str) -> str:
    """
    Normalize street/road name with USPS abbreviations.

    Applies:
    - Street suffix abbreviations (STREET → ST)
    - Directional abbreviations (NORTH → N)

    Args:
        road: Raw road name

    Returns:
        Normalized road name
    """
    # Split into words
    words = road.upper().split()
    normalized_words = []

    for word in words:
        # Check if it's a street suffix
        if word in STREET_SUFFIXES:
            normalized_words.append(STREET_SUFFIXES[word])
        # Check if it's a directional
        elif word in DIRECTIONALS:
            normalized_words.append(DIRECTIONALS[word])
        else:
            normalized_words.append(word)

    return ' '.join(normalized_words)


def _normalize_secondary_unit(unit_str: str) -> str:
    """
    Normalize secondary unit designations (suite, apartment, floor, etc.).

    Applies SECONDARY_UNITS abbreviations:
    - SUITE → STE
    - APARTMENT → APT
    - FLOOR → FL

    Args:
        unit_str: Raw unit string (e.g., "suite 301", "floor 3")

    Returns:
        Normalized unit string (e.g., "STE 301", "FL 3")
    """
    words = unit_str.upper().split()
    normalized_words = []

    for word in words:
        if word in SECONDARY_UNITS:
            normalized_words.append(SECONDARY_UNITS[word])
        else:
            normalized_words.append(word)

    return ' '.join(normalized_words)


def _normalize_city(components: Dict[str, str]) -> str:
    """
    Normalize city name.

    Args:
        components: Parsed address components

    Returns:
        City name in ALL CAPS
    """
    city = components.get('city', '')
    return _clean_and_caps(city)


def _normalize_state(components: Dict[str, str]) -> str:
    """
    Normalize state to 2-letter code.

    Handles:
    - Full state names (CALIFORNIA → CA)
    - Already abbreviated (CA → CA)

    Args:
        components: Parsed address components

    Returns:
        2-letter state code in ALL CAPS
    """
    state = components.get('state', '')
    state_upper = state.upper()

    # Check if it's a full state name
    if state_upper in STATE_CODES:
        return STATE_CODES[state_upper]

    # Already a code (or unknown)
    return state_upper


def _normalize_zip(components: Dict[str, str]) -> str:
    """
    Normalize ZIP code.

    Handles:
    - 5-digit ZIP
    - 9-digit ZIP+4

    Args:
        components: Parsed address components

    Returns:
        ZIP code string
    """
    return components.get('postcode', '')


def _clean_and_caps(text: str) -> str:
    """
    Apply USPS cleaning rules:
    - Convert to ALL CAPS
    - Remove periods
    - Remove extra whitespace

    Args:
        text: Input text

    Returns:
        Cleaned and capitalized text
    """
    # Convert to uppercase
    text = text.upper()

    # Remove periods
    text = text.replace('.', '')

    # Remove extra whitespace
    text = re.sub(r'\s+', ' ', text)

    # Strip leading/trailing whitespace
    text = text.strip()

    return text
