import xml.etree.ElementTree as ET
from typing import List
from .models import AbnRecordV1, AbnLookupError, AddressV1

XML_NS = {"ns": "http://abr.business.gov.au/ABRXMLSearch/"}


def _find_business_entity(root: ET.Element) -> ET.Element:
    # Intenta distintas versiones de businessEntity
    for tag in [
        "businessEntity202001",
        "businessEntity201408",
        "businessEntity201205",
        "businessEntity200709",
        "businessEntity200506",
        "businessEntity",
    ]:
        entity = root.find(f".//ns:{tag}", XML_NS)
        if entity is not None:
            return entity
    raise AbnLookupError("No se encontró ningún nodo businessEntity en la respuesta XML.")


def _check_exception(root: ET.Element) -> None:
    exception = root.find(".//ns:exception", XML_NS)
    if exception is None:
        return

    code = exception.findtext("ns:exceptionCode", default="", namespaces=XML_NS)
    desc = exception.findtext("ns:exceptionDescription", default="", namespaces=XML_NS)
    raise AbnLookupError(f"Error desde ABR: {code} - {desc}")


def _extract_address(entity: ET.Element, tag_name: str) -> AddressV1:
    """
    Extrae una dirección (física principal o postal) en formato AddressV1.
    """
    addr = entity.find(f"ns:{tag_name}", XML_NS)
    if addr is None:
        return AddressV1()

    return AddressV1(
        line1=addr.findtext("ns:addressLine1", default="", namespaces=XML_NS) or "",
        line2=addr.findtext("ns:addressLine2", default="", namespaces=XML_NS) or "",
        line3=addr.findtext("ns:addressLine3", default="", namespaces=XML_NS) or "",
        line4=addr.findtext("ns:addressLine4", default="", namespaces=XML_NS) or "",
        suburb=addr.findtext("ns:suburb", default="", namespaces=XML_NS) or "",
        state_code=addr.findtext("ns:stateCode", default="", namespaces=XML_NS) or "",
        postcode=addr.findtext("ns:postcode", default="", namespaces=XML_NS) or "",
        country_code=addr.findtext("ns:countryCode", default="", namespaces=XML_NS) or "",
    )


def _extract_legal_name(entity: ET.Element) -> str:
    """
    Arma un nombre legal para individuos (cuando aplica).
    Para compañías normalmente estará vacío.
    """
    family = entity.findtext("ns:legalName/ns:familyName", default="", namespaces=XML_NS)
    given = entity.findtext("ns:legalName/ns:givenName", default="", namespaces=XML_NS)
    other = entity.findtext("ns:legalName/ns:otherGivenName", default="", namespaces=XML_NS)

    parts = [p for p in [given, other, family] if p]
    return " ".join(parts)


def _extract_business_names(entity: ET.Element) -> List[str]:
    names: List[str] = []
    for bn in entity.findall("ns:businessName", XML_NS):
        name = bn.findtext("ns:organisationName", default="", namespaces=XML_NS) or ""
        if name:
            names.append(name)
    return names


def parse_abn_v1(xml_str: str) -> AbnRecordV1:
    """
    Parsea el XML devuelto por ABR y devuelve un AbnRecordV1.
    Extrae la mayor cantidad posible de campos (versión 1 extendida).
    """
    root = ET.fromstring(xml_str)

    # Verificar si el servicio devolvió una excepción
    _check_exception(root)

    entity = _find_business_entity(root)

    # ABN
    abn = entity.findtext("ns:ABN/ns:identifierValue", default="", namespaces=XML_NS).strip()

    # ACN / ASIC number (si existe)
    acn = entity.findtext("ns:ASICNumber/ns:identifierValue", default="", namespaces=XML_NS)
    if acn:
        acn = acn.strip()
    else:
        acn = None

    # Estado del ABN
    status_code = entity.findtext("ns:entityStatus/ns:entityStatusCode", default="", namespaces=XML_NS) or ""
    status_from = entity.findtext("ns:entityStatus/ns:effectiveFrom", default="", namespaces=XML_NS) or ""

    # Tipo de entidad
    entity_type_code = entity.findtext("ns:entityType/ns:entityTypeCode", default="", namespaces=XML_NS) or ""
    entity_type_desc = entity.findtext("ns:entityType/ns:entityDescription", default="", namespaces=XML_NS) or ""

    # Nombre principal (organización)
    main_name = entity.findtext("ns:mainName/ns:organisationName", default="", namespaces=XML_NS) or ""

    # Nombre legal (para individuos)
    legal_name = _extract_legal_name(entity)

    # Nombres de negocio (business names)
    business_names = _extract_business_names(entity)

    # Direcciones
    main_business_address = _extract_address(entity, "mainBusinessPhysicalAddress")
    postal_address = _extract_address(entity, "mainPostalAddress")

    # GST
    gst_node = entity.find("ns:goodsAndServicesTax", XML_NS)
    gst_from = None
    gst_to = None
    gst_registered = False

    if gst_node is not None:
        gst_from = gst_node.findtext("ns:effectiveFrom", default="", namespaces=XML_NS) or None
        gst_to = gst_node.findtext("ns:effectiveTo", default="", namespaces=XML_NS) or None
        gst_registered = bool(gst_from)

    # Fecha de última actualización (si existe)
    last_updated = entity.findtext("ns:recordLastUpdatedDate", default="", namespaces=XML_NS)
    if last_updated:
        last_updated = last_updated.strip()
    else:
        # fallback a un campo más global si existe
        last_updated = root.findtext(".//ns:dateRegisterLastUpdated", default="", namespaces=XML_NS) or None

    return AbnRecordV1(
        abn=abn,
        acn=acn,
        main_name=main_name,
        legal_name=legal_name,
        business_names=business_names,
        status_code=status_code,
        status_from=status_from,
        entity_type_code=entity_type_code,
        entity_type_desc=entity_type_desc,
        main_business_address=main_business_address,
        postal_address=postal_address,
        gst_registered=gst_registered,
        gst_effective_from=gst_from,
        gst_effective_to=gst_to,
        last_updated_date=last_updated,
    )
