"""
Compatibility layer for pycares 5.x API.

This module provides result types compatible with pycares 4.x API
to maintain backward compatibility with existing code.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Union, cast

import pycares

from . import error

_SINGLE_RESULT_QTYPES = frozenset(
    {
        pycares.QUERY_TYPE_CNAME,
        pycares.QUERY_TYPE_SOA,
        pycares.QUERY_TYPE_PTR,
    }
)


def _maybe_str(data: bytes) -> str | bytes:
    """Decode bytes as ASCII, return bytes if decode fails (pycares 4.x)."""
    try:
        return data.decode('ascii')
    except UnicodeDecodeError:
        return data


@dataclass(frozen=True, slots=True)
class AresQueryAResult:
    """A record result (compatible with pycares 4.x ares_query_a_result)."""

    host: str
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryAAAAResult:
    """AAAA record result (pycares 4.x compat)."""

    host: str
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryCNAMEResult:
    """CNAME record result (pycares 4.x compat)."""

    cname: str
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryMXResult:
    """MX record result (pycares 4.x compat)."""

    host: str
    priority: int
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryNSResult:
    """NS record result (pycares 4.x compat)."""

    host: str
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryTXTResult:
    """TXT record result (pycares 4.x compat)."""

    text: str | bytes  # str if ASCII, bytes otherwise (pycares 4.x behavior)
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQuerySOAResult:
    """SOA record result (pycares 4.x compat)."""

    nsname: str
    hostmaster: str
    serial: int
    refresh: int
    retry: int
    expires: int
    minttl: int
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQuerySRVResult:
    """SRV record result (pycares 4.x compat)."""

    host: str
    port: int
    priority: int
    weight: int
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryNAPTRResult:
    """NAPTR record result (pycares 4.x compat)."""

    order: int
    preference: int
    flags: str
    service: str
    regex: str
    replacement: str
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryCAAResult:
    """CAA record result (pycares 4.x compat)."""

    critical: int
    property: str
    value: str
    ttl: int


@dataclass(frozen=True, slots=True)
class AresQueryPTRResult:
    """PTR record result (pycares 4.x compat)."""

    name: str
    ttl: int
    aliases: list[str]


@dataclass(frozen=True, slots=True)
class AresHostResult:
    """Host result (compatible with pycares 4.x ares_host_result)."""

    name: str
    aliases: list[str]
    addresses: list[str]


# Type alias for a single converted record
ConvertedRecord = Union[
    AresQueryAResult,
    AresQueryAAAAResult,
    AresQueryCNAMEResult,
    AresQueryMXResult,
    AresQueryNSResult,
    AresQueryTXTResult,
    AresQuerySOAResult,
    AresQuerySRVResult,
    AresQueryNAPTRResult,
    AresQueryCAAResult,
    AresQueryPTRResult,
    pycares.DNSRecord,  # Unknown types returned as-is
]

# Type alias for query results
QueryResult = Union[
    list[AresQueryAResult],
    list[AresQueryAAAAResult],
    AresQueryCNAMEResult,
    list[AresQueryMXResult],
    list[AresQueryNSResult],
    list[AresQueryTXTResult],
    AresQuerySOAResult,
    list[AresQuerySRVResult],
    list[AresQueryNAPTRResult],
    list[AresQueryCAAResult],
    AresQueryPTRResult,
    list[ConvertedRecord],  # For ANY query type
]


def _convert_record(record: pycares.DNSRecord) -> ConvertedRecord:
    """Convert a single DNS record to pycares 4.x compatible format."""
    ttl = record.ttl
    record_type = record.type

    if record_type == pycares.QUERY_TYPE_A:
        a_data = cast(pycares.ARecordData, record.data)
        return AresQueryAResult(host=a_data.addr, ttl=ttl)
    if record_type == pycares.QUERY_TYPE_AAAA:
        aaaa_data = cast(pycares.AAAARecordData, record.data)
        return AresQueryAAAAResult(host=aaaa_data.addr, ttl=ttl)
    if record_type == pycares.QUERY_TYPE_CNAME:
        cname_data = cast(pycares.CNAMERecordData, record.data)
        return AresQueryCNAMEResult(cname=cname_data.cname, ttl=ttl)
    if record_type == pycares.QUERY_TYPE_MX:
        mx_data = cast(pycares.MXRecordData, record.data)
        return AresQueryMXResult(
            host=mx_data.exchange, priority=mx_data.priority, ttl=ttl
        )
    if record_type == pycares.QUERY_TYPE_NS:
        ns_data = cast(pycares.NSRecordData, record.data)
        return AresQueryNSResult(host=ns_data.nsdname, ttl=ttl)
    if record_type == pycares.QUERY_TYPE_TXT:
        txt_data = cast(pycares.TXTRecordData, record.data)
        return AresQueryTXTResult(text=_maybe_str(txt_data.data), ttl=ttl)
    if record_type == pycares.QUERY_TYPE_SOA:
        soa_data = cast(pycares.SOARecordData, record.data)
        return AresQuerySOAResult(
            nsname=soa_data.mname,
            hostmaster=soa_data.rname,
            serial=soa_data.serial,
            refresh=soa_data.refresh,
            retry=soa_data.retry,
            expires=soa_data.expire,
            minttl=soa_data.minimum,
            ttl=ttl,
        )
    if record_type == pycares.QUERY_TYPE_SRV:
        srv_data = cast(pycares.SRVRecordData, record.data)
        return AresQuerySRVResult(
            host=srv_data.target,
            port=srv_data.port,
            priority=srv_data.priority,
            weight=srv_data.weight,
            ttl=ttl,
        )
    if record_type == pycares.QUERY_TYPE_NAPTR:
        naptr_data = cast(pycares.NAPTRRecordData, record.data)
        return AresQueryNAPTRResult(
            order=naptr_data.order,
            preference=naptr_data.preference,
            flags=naptr_data.flags,
            service=naptr_data.service,
            regex=naptr_data.regexp,
            replacement=naptr_data.replacement,
            ttl=ttl,
        )
    if record_type == pycares.QUERY_TYPE_CAA:
        caa_data = cast(pycares.CAARecordData, record.data)
        return AresQueryCAAResult(
            critical=caa_data.critical,
            property=caa_data.tag,
            value=caa_data.value,
            ttl=ttl,
        )
    if record_type == pycares.QUERY_TYPE_PTR:
        ptr_data = cast(pycares.PTRRecordData, record.data)
        return AresQueryPTRResult(name=ptr_data.dname, ttl=ttl, aliases=[])
    # Return raw record for unknown types
    return record


def convert_result(dns_result: pycares.DNSResult, qtype: int) -> QueryResult:
    """Convert pycares 5.x DNSResult to pycares 4.x compatible format."""
    # For ANY - convert all records and return mixed list
    if qtype == pycares.QUERY_TYPE_ANY:
        return [_convert_record(record) for record in dns_result.answer]

    results: list[ConvertedRecord] = []

    for record in dns_result.answer:
        record_type = record.type

        # Filter by query type since answer can contain other types
        # (e.g., CNAME records when querying for A/AAAA)
        if record_type != qtype:
            continue

        converted = _convert_record(record)

        # CNAME, SOA, and PTR return single result, not list
        if record_type in _SINGLE_RESULT_QTYPES:
            return cast(QueryResult, converted)

        results.append(converted)

    # NOERROR/NODATA: c-ares delivered ARES_SUCCESS but the answer has no
    # records of the queried type. pycares 4.x raised ARES_ENODATA here;
    # without this branch single-result qtypes (CNAME/SOA/PTR) would
    # resolve to [] and crash callers reading .name/.cname/.nsname.
    if not results:
        raise error.DNSError(
            pycares.errno.ARES_ENODATA,
            pycares.errno.strerror(pycares.errno.ARES_ENODATA),
        )

    return results
