"""Lee el ctag de una coleccion DAV en UNA peticion barata (PROPFIND Depth:0).

Funcion impura: hace un unico PROPFIND Depth:0 sobre la coleccion pidiendo el
`getctag` de CalendarServer (`http://calendarserver.org/ns/`) y, como respaldo,
el `getetag` DAV de la propia coleccion. El ctag es un token opaco que cambia
SOLO cuando cambia algun recurso de la coleccion: comparandolo con el ctag
cacheado se decide si hay que recargar el contenido (REPORT) o servir de cache
sin tocar la red.

Construye el header `Authorization: Basic base64(user:pass)` a mano con stdlib y
parsea el multistatus con regex simple. Maneja errores sin lanzar. Solo usa
stdlib (urllib, base64, re, ssl). Probado contra Xandikos.
"""

import base64
import re
import ssl
import urllib.error
import urllib.request

_CTAG_RE = re.compile(
    r"<(?:[A-Za-z0-9]+:)?getctag>\s*(.*?)\s*</(?:[A-Za-z0-9]+:)?getctag>",
    re.DOTALL | re.IGNORECASE,
)
_ETAG_RE = re.compile(
    r"<(?:[A-Za-z0-9]+:)?getetag>\s*(.*?)\s*</(?:[A-Za-z0-9]+:)?getetag>",
    re.DOTALL | re.IGNORECASE,
)

_PROPFIND_BODY = (
    '<?xml version="1.0" encoding="utf-8" ?>'
    '<D:propfind xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">'
    "<D:prop><CS:getctag/><D:getetag/></D:prop>"
    "</D:propfind>"
)


def _basic_auth_header(username: str, password: str) -> str:
    raw = ("%s:%s" % (username, password)).encode("utf-8")
    return "Basic " + base64.b64encode(raw).decode("ascii")


def _join_url(base_url: str, collection_path: str) -> str:
    return base_url.rstrip("/") + "/" + collection_path.strip("/") + "/"


def dav_collection_ctag(
    base_url: str,
    username: str,
    password: str,
    collection_path: str,
    *,
    timeout_s: float = 10.0,
    verify_tls: bool = True,
) -> dict:
    """Lee el ctag (token de version) de una coleccion DAV.

    Args:
        base_url: URL base del servidor DAV.
        username: usuario para HTTP Basic auth.
        password: contrasena para HTTP Basic auth. Resolver desde pass.
        collection_path: ruta de la coleccion (CardDAV o CalDAV).
        timeout_s: timeout de la peticion en segundos. Default 10.0.
        verify_tls: si True (default) verifica el certificado TLS.

    Returns:
        dict. En exito: {status:'ok', http_status:int, ctag:str} donde ctag es
        el getctag de CalendarServer si el servidor lo expone, o el getetag DAV
        de la coleccion como respaldo. En error (sin lanzar):
        {status:'error', error:str, http_status:int|None}.
    """
    url = _join_url(base_url, collection_path)
    headers = {
        "Authorization": _basic_auth_header(username, password),
        "Content-Type": "application/xml; charset=utf-8",
        "Depth": "0",
    }
    req = urllib.request.Request(
        url, data=_PROPFIND_BODY.encode("utf-8"), method="PROPFIND", headers=headers
    )

    context = None if verify_tls else ssl._create_unverified_context()
    try:
        with urllib.request.urlopen(req, timeout=timeout_s, context=context) as resp:
            status = resp.status
            xml = resp.read().decode("utf-8", "replace")
    except urllib.error.HTTPError as e:
        return {"status": "error", "error": "http %s" % e.code, "http_status": e.code}
    except urllib.error.URLError as e:
        return {"status": "error", "error": str(e.reason), "http_status": None}
    except Exception as e:  # noqa: BLE001
        return {"status": "error", "error": str(e), "http_status": None}

    ctag_m = _CTAG_RE.search(xml)
    if ctag_m:
        return {"status": "ok", "http_status": status, "ctag": ctag_m.group(1).strip()}
    etag_m = _ETAG_RE.search(xml)
    if etag_m:
        return {"status": "ok", "http_status": status, "ctag": etag_m.group(1).strip()}
    return {
        "status": "error",
        "error": "ni getctag ni getetag en la respuesta",
        "http_status": status,
    }
