from __future__ import annotations

import base64
import time
import urllib.error
import urllib.request
from typing import Callable
from urllib.parse import urljoin


HttpOpener = Callable[..., object]


def _basic_auth_header(username: str | None, password: str | None) -> dict[str, str]:
    if username is None:
        return {}
    token = base64.b64encode(f"{username}:{password or ''}".encode()).decode("ascii")
    return {"Authorization": f"Basic {token}"}


def webdav_mkcol(
    *,
    base_url: str,
    collection_path: str,
    username: str | None = None,
    password: str | None = None,
    http_opener: HttpOpener | None = None,
    timeout: float = 30.0,
) -> bool:
    """MKCOL 创建 WebDAV 目录（已存在视为 ok）。"""
    base = base_url.rstrip("/") + "/"
    path = collection_path.strip("/")
    url = urljoin(base, path + "/" if path else "")
    opener = http_opener or urllib.request.urlopen
    headers = _basic_auth_header(username, password)
    req = urllib.request.Request(url, method="MKCOL", headers=headers)
    try:
        with opener(req, timeout=timeout) as resp:
            status = getattr(resp, "status", 201)
            return status in (201, 204, 405)
    except urllib.error.HTTPError as exc:
        if exc.code in (201, 204, 405, 409):
            return True
        raise


def webdav_put(
    *,
    base_url: str,
    remote_path: str,
    rel_name: str,
    data: bytes,
    username: str | None = None,
    password: str | None = None,
    http_opener: HttpOpener | None = None,
    timeout: float = 60.0,
    retry_attempts: int = 1,
    ensure_collections: bool = False,
) -> bool:
    """PUT 文件到 WebDAV 目录（极空间等）。"""
    base = base_url.rstrip("/") + "/"
    path = remote_path.strip("/")
    if ensure_collections and path:
        parts = path.split("/")
        for i in range(1, len(parts) + 1):
            webdav_mkcol(
                base_url=base_url,
                collection_path="/".join(parts[:i]),
                username=username,
                password=password,
                http_opener=http_opener,
                timeout=timeout,
            )
    url = urljoin(base, f"{path}/{rel_name}" if path else rel_name)
    opener = http_opener or urllib.request.urlopen
    headers = {"Content-Type": "application/json", **_basic_auth_header(username, password)}
    req = urllib.request.Request(url, data=data, method="PUT", headers=headers)
    last_err: Exception | None = None
    attempts = max(1, int(retry_attempts))
    for attempt in range(attempts):
        try:
            with opener(req, timeout=timeout) as resp:
                status = getattr(resp, "status", 200)
                return status < 400
        except urllib.error.HTTPError as exc:
            if exc.code in (201, 204):
                return True
            last_err = exc
        except Exception as exc:
            last_err = exc
        if attempt < attempts - 1:
            time.sleep(0.2 * (2**attempt))
    if last_err:
        raise last_err
    return False
