from __future__ import annotations

import logging
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Callable, Protocol
from urllib.parse import quote

logger = logging.getLogger(__name__)

DEFAULT_CLOUD_REMOTE_ROOT = "apps/念念/data"


def resolve_remote_root(config: dict | None, provider_cfg: dict | None = None) -> str:
    """三云统一的网盘远端根路径（相对网盘根目录）。"""
    cfg = provider_cfg or {}
    if cfg.get("remote_root"):
        return str(cfg["remote_root"]).strip("/")
    storage = (config or {}).get("storage") or {}
    cloud = storage.get("cloud") or {}
    if cloud.get("remote_root"):
        return str(cloud["remote_root"]).strip("/")
    return DEFAULT_CLOUD_REMOTE_ROOT


def parse_remote_root_parts(remote_root: str | None) -> list[str]:
    raw = (remote_root or DEFAULT_CLOUD_REMOTE_ROOT).strip("/").replace("\\", "/")
    parts = [part for part in raw.split("/") if part]
    if not parts:
        raise ValueError("remote_root must not be empty")
    return parts


class CloudBackend(Protocol):
    name: str

    def is_configured(self) -> bool: ...

    def status(self) -> str: ...

    def upload(self, rel_path: str, data: bytes) -> bool: ...

    def download(self, rel_path: str) -> bytes | None: ...


def resolve_cloud_root(home: Path, name: str, cfg: dict | None) -> Path:
    """L1 根目录：providers root_dir > 默认 {home}/.cloud-l1/{name}。"""
    cfg = cfg or {}
    raw = cfg.get("root_dir")
    if raw:
        return Path(str(raw)).expanduser().resolve()
    return (home / ".cloud-l1" / name).resolve()


class FileCloudBackend:
    """文件镜像 L1 后端 — 无 token 时用于开发/冒烟；有 token 时同样走镜像直至接真实 API。"""

    name = "file"

    def __init__(self, backend_name: str, root: Path, *, token: str | None = None) -> None:
        self.backend_name = backend_name
        self.root = root
        self.token = token
        self.root.mkdir(parents=True, exist_ok=True)

    @property
    def name(self) -> str:
        return self.backend_name

    def is_configured(self) -> bool:
        return bool(self.token) or self.root.is_dir()

    def status(self) -> str:
        if not self.is_configured():
            return "unconfigured"
        if self.token:
            return "ready"
        return "mirror"

    def primary_mode(self) -> str:
        if not self.is_configured():
            return "unconfigured"
        if self.token:
            return "live"
        return "mirror"

    def upload(self, rel_path: str, data: bytes) -> bool:
        if not self.is_configured():
            return False
        dest = self.root / rel_path
        dest.parent.mkdir(parents=True, exist_ok=True)
        dest.write_bytes(data)
        return True

    def download(self, rel_path: str) -> bytes | None:
        dest = self.root / rel_path
        if dest.is_file():
            return dest.read_bytes()
        return None


HttpOpener = Callable[..., object]


class LiveCloudBackend:
    """Live HTTP 后端 — 失败时降级 mirror 目录；401 时 Vault refresh 一次。"""

    def __init__(
        self,
        backend_name: str,
        mirror_root: Path,
        *,
        token: str | None,
        api_base: str,
        timeout: float = 30.0,
        max_retries: int = 2,
        http_opener: HttpOpener | None = None,
        credential_manager: object | None = None,
        provider: str | None = None,
        remote_root: str | None = None,
    ) -> None:
        self.backend_name = backend_name
        self.token = token
        self.api_base = api_base.rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries
        self._http_opener = http_opener or urllib.request.urlopen
        self._mirror = FileCloudBackend(backend_name, mirror_root, token=token)
        self._credential_manager = credential_manager
        self._provider = provider or backend_name
        self._remote_root = resolve_remote_root(None, {"remote_root": remote_root})
        self._last_upload_degraded = False
        self._last_live_failed = False

    @property
    def name(self) -> str:
        return self.backend_name

    @property
    def root(self) -> Path:
        return self._mirror.root

    @property
    def last_upload_degraded(self) -> bool:
        return self._last_upload_degraded

    def is_configured(self) -> bool:
        return bool(self.token) or self._mirror.root.is_dir()

    def status(self) -> str:
        if not self.is_configured():
            return "unconfigured"
        if self._last_live_failed:
            return "degraded"
        if self.token:
            return "ready"
        return "mirror"

    def primary_mode(self) -> str:
        if not self.token:
            return "mirror" if self._mirror.root.is_dir() else "unconfigured"
        return "live"

    def upload(self, rel_path: str, data: bytes) -> bool:
        if not self.is_configured():
            return False
        live_ok = False
        self._last_live_failed = False
        if self.token:
            try:
                self._remote_put(rel_path, data)
                live_ok = True
            except Exception as exc:
                self._last_live_failed = True
                logger.warning("%s live upload failed for %s: %s", self.backend_name, rel_path, exc)
        mirror_ok = self._mirror.upload(rel_path, data)
        self._last_upload_degraded = self._last_live_failed and mirror_ok
        return live_ok or mirror_ok

    def download(self, rel_path: str) -> bytes | None:
        if self.token:
            try:
                data = self._remote_get(rel_path)
                if data is not None:
                    return data
            except Exception as exc:
                logger.warning("%s live download failed for %s: %s", self.backend_name, rel_path, exc)
        return self._mirror.download(rel_path)

    def _object_url(self, rel_path: str) -> str:
        prefix = quote(self._remote_root, safe="/")
        return f"{self.api_base}/1/clouddrive/{prefix}/v1/object/{quote(rel_path, safe='')}"

    def _auth_headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/octet-stream",
            "User-Agent": "niannian/0.2.0",
        }

    def _request(self, req: urllib.request.Request, *, auth_retry: bool = True) -> bytes:
        last_err: Exception | None = None
        for attempt in range(self.max_retries + 1):
            try:
                with self._http_opener(req, timeout=self.timeout) as resp:
                    status = getattr(resp, "status", 200)
                    body = resp.read()
                    if status >= 400:
                        raise urllib.error.HTTPError(
                            req.full_url, status, body.decode("utf-8", "replace"), resp.headers, None
                        )
                    return body
            except urllib.error.HTTPError as exc:
                last_err = exc
                if (
                    auth_retry
                    and exc.code == 401
                    and self._credential_manager is not None
                    and hasattr(self._credential_manager, "refresh")
                ):
                    refresh = self._credential_manager.refresh(self._provider)
                    if refresh.ok:
                        new_token = self._credential_manager.get_access_token(self._provider)
                        if self._provider == "feimao" and hasattr(self._credential_manager, "get_session_token"):
                            new_token = self._credential_manager.get_session_token("feimao") or new_token
                        if new_token:
                            self.token = new_token
                            headers = dict(req.header_items())
                            headers["Authorization"] = f"Bearer {new_token}"
                            retry_req = urllib.request.Request(
                                req.full_url,
                                data=getattr(req, "data", None),
                                method=req.get_method(),
                                headers=headers,
                            )
                            return self._request(retry_req, auth_retry=False)
                    if hasattr(self._credential_manager, "mark_needs_relogin"):
                        self._credential_manager.mark_needs_relogin(self._provider, error="auth_failed")
                if attempt < self.max_retries:
                    time.sleep(0.2 * (attempt + 1))
                elif last_err is exc:
                    raise
            except Exception as exc:
                last_err = exc
                if attempt < self.max_retries:
                    time.sleep(0.2 * (attempt + 1))
        assert last_err is not None
        raise last_err

    def _remote_put(self, rel_path: str, data: bytes) -> None:
        req = urllib.request.Request(
            self._object_url(rel_path),
            data=data,
            method="PUT",
            headers=self._auth_headers(),
        )
        self._request(req)

    def _remote_get(self, rel_path: str) -> bytes | None:
        req = urllib.request.Request(
            self._object_url(rel_path),
            method="GET",
            headers={"Authorization": f"Bearer {self.token}", "User-Agent": "niannian/0.2.0"},
        )
        try:
            return self._request(req)
        except urllib.error.HTTPError as exc:
            if exc.code == 404:
                return None
            raise
