"""
Telephony helper utilities.
Common functions used across telephony operations.
"""

import inspect

from fastapi import Request
from starlette.responses import HTMLResponse

from api.constants import COUNTRY_CODES


def numbers_match(
    incoming_number: str,
    configured_number: str,
    to_country: str = None,
    from_country: str = None,
) -> bool:
    """
    Check if two phone numbers match, handling different formats with country context.

    Args:
        incoming_number: Phone number from webhook
        configured_number: Phone number from organization config
        to_country: ISO country code for the called number (e.g., "US", "IN")
        from_country: ISO country code for the caller (e.g., "IN", "GB")

    Examples:
    - incoming: "+08043071383", configured: "918043071383", to_country="IN" -> True
    - incoming: "+918043071383", configured: "918043071383" -> True
    - incoming: "+19781899185", configured: "+19781899185" -> True
    """
    if not incoming_number or not configured_number:
        return False

    # Remove spaces and normalize
    incoming_clean = incoming_number.replace(" ", "").replace("-", "")
    configured_clean = configured_number.replace(" ", "").replace("-", "")

    # Direct match
    if incoming_clean == configured_clean:
        return True

    # Remove + from both and compare
    incoming_no_plus = incoming_clean.lstrip("+")
    configured_no_plus = configured_clean.lstrip("+")

    if incoming_no_plus == configured_no_plus:
        return True

    if to_country:
        country_code = get_country_code(to_country)
        if country_code:
            if _test_number_formats_with_country_code(
                incoming_no_plus, configured_no_plus, country_code
            ):
                return True

    # Fallback to caller country if available
    if from_country and from_country != to_country:
        country_code = get_country_code(from_country)
        if country_code:
            if _test_number_formats_with_country_code(
                incoming_no_plus, configured_no_plus, country_code
            ):
                return True

    # Legacy fallback for common country codes (when no country info available)
    if not to_country and not from_country:
        common_codes = ["91", "1", "44"]  # India, US/Canada, UK
        for code in common_codes:
            if _test_number_formats_with_country_code(
                incoming_no_plus, configured_no_plus, code
            ):
                return True

    return False


def _test_number_formats_with_country_code(
    incoming_no_plus: str, configured_no_plus: str, country_code: str
) -> bool:
    """
    Test different phone number format variations with the given country code to find matches.

    This function handles various international phone number formatting scenarios:
    - Numbers with/without country codes
    - Numbers with leading zeros vs country codes
    - Different representations of the same number across formats

    Args:
        incoming_no_plus: Incoming number without + prefix
        configured_no_plus: Configured number without + prefix
        country_code: International dialing code (e.g., "91", "1")

    Returns:
        True if any format variation produces a match
    """
    # Case 1: Incoming has no country code, configured has it
    if f"{country_code}{incoming_no_plus}" == configured_no_plus:
        return True

    # Case 2: Incoming has leading 0, need to replace with country code
    if incoming_no_plus.startswith("0"):
        local_part = incoming_no_plus[1:]  # Remove leading 0
        if f"{country_code}{local_part}" == configured_no_plus:
            return True

    # Case 3: Configured has no country code, incoming has it
    if f"{country_code}{configured_no_plus}" == incoming_no_plus:
        return True

    # Case 4: Configured has leading 0, need to replace with country code
    if configured_no_plus.startswith("0"):
        local_part = configured_no_plus[1:]  # Remove leading 0
        if f"{country_code}{local_part}" == incoming_no_plus:
            return True

    return False


def normalize_webhook_data(provider_class, webhook_data, headers=None):
    """Normalize webhook data using the provider's parse method"""
    parse_method = provider_class.parse_inbound_webhook
    if headers is not None and "headers" in inspect.signature(parse_method).parameters:
        return parse_method(webhook_data, headers=headers)
    return parse_method(webhook_data)


def generic_hangup_response():
    """Return a generic hangup response for unknown/error cases"""
    return HTMLResponse(
        content="<Response><Hangup/></Response>", media_type="application/xml"
    )


async def parse_webhook_request(request: Request) -> tuple[dict, str]:
    """Parse webhook request data from JSON, form, or query string.

    Returns ``(webhook_data, raw_body)`` where ``raw_body`` is the
    request body decoded as UTF-8 — kept around for providers (e.g.
    Vobiz) whose signature is computed over the raw bytes.

    Some providers (Exotel Voicebot dynamic URL) call the webhook as GET
    with call context in query parameters. Starlette's ``request.form()``
    returns an empty ``FormData`` for non-form content types instead of
    raising, so query params must be applied whenever JSON/form parsing
    yields no fields — not only in the ``except`` path.
    """
    raw_body = (await request.body()).decode("utf-8", errors="replace")
    try:
        webhook_data = await request.json()
    except Exception:
        try:
            webhook_data = dict(await request.form())
        except Exception:
            webhook_data = {}

    if not webhook_data:
        webhook_data = dict(request.query_params)
    if not webhook_data:
        raise ValueError("Unable to parse webhook data")

    return webhook_data, raw_body


def get_country_code(country_iso: str) -> str:
    """
    Get the international dialing code for a country.

    Args:
        country_iso: ISO 3166-1 alpha-2 country code (e.g., "US", "IN", "GB")

    Returns:
        International dialing code (e.g., "1", "91", "44") or empty string if not found
    """
    if not country_iso:
        return ""

    return COUNTRY_CODES.get(country_iso.upper(), "")


def get_countries_for_code(dialing_code: str) -> list[str]:
    """
    Get all countries that use a specific dialing code.

    Args:
        dialing_code: International dialing code (e.g., "1", "91")

    Returns:
        List of ISO country codes that use this dialing code
    """
    if not dialing_code:
        return []

    return [country for country, code in COUNTRY_CODES.items() if code == dialing_code]
