from __future__ import annotations

import json
from dataclasses import dataclass, field

from niannian.storage.cloud_base import CloudBackend
from niannian.storage.manifest import Manifest


@dataclass
class DualUploadResult:
    rel_path: str
    quark_ok: bool
    baidu_ok: bool
    onedrive_ok: bool = False
    feimao_ok: bool = False

    @property
    def ok(self) -> bool:
        return self.quark_ok or self.baidu_ok or self.onedrive_ok or self.feimao_ok


@dataclass
class DualWriteReport:
    uploads: list[DualUploadResult] = field(default_factory=list)
    errors: list[str] = field(default_factory=list)

    @property
    def ok(self) -> bool:
        return bool(self.uploads) and all(u.ok for u in self.uploads)


class CloudDualBackend:
    """L1 夸克 + 百度双写协调。"""

    MANIFEST_PATH = "sync/manifest.json"

    def __init__(self, quark: CloudBackend, baidu: CloudBackend) -> None:
        self._quark = quark
        self._baidu = baidu
        self._last_degraded = False

    def primary_mode(self) -> str:
        modes = {self._quark.primary_mode(), self._baidu.primary_mode()}
        if modes == {"live"}:
            return "live"
        if self._quark.is_configured() and self._baidu.is_configured():
            return "mirror"
        return "mirror"

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

    @property
    def quark(self) -> CloudBackend:
        return self._quark

    @property
    def baidu(self) -> CloudBackend:
        return self._baidu

    def cloud_ready(self) -> bool:
        return self._quark.is_configured() and self._baidu.is_configured()

    def backend_status(self) -> dict[str, str]:
        return {
            "quark": self._quark.status(),
            "baidu": self._baidu.status(),
        }

    def dual_upload(self, rel_path: str, data: bytes) -> DualUploadResult:
        q_ok = self._quark.upload(rel_path, data)
        b_ok = self._baidu.upload(rel_path, data)
        q_degraded = bool(getattr(self._quark, "last_upload_degraded", False))
        b_degraded = bool(getattr(self._baidu, "last_upload_degraded", False))
        self._last_degraded = ((q_ok != b_ok) and (q_ok or b_ok)) or q_degraded or b_degraded
        return DualUploadResult(rel_path=rel_path, quark_ok=q_ok, baidu_ok=b_ok)

    def dual_download(self, rel_path: str) -> bytes | None:
        data = self._quark.download(rel_path)
        if data is not None:
            return data
        return self._baidu.download(rel_path)

    def read_remote_manifest(self) -> Manifest | None:
        raw = self.dual_download(self.MANIFEST_PATH)
        if raw is None:
            return None
        try:
            return Manifest.from_dict(json.loads(raw.decode("utf-8")))
        except (json.JSONDecodeError, KeyError, TypeError, ValueError):
            return None

    def upload_manifest(self, manifest: Manifest) -> DualUploadResult:
        data = (json.dumps(manifest.to_dict(), ensure_ascii=False, indent=2) + "\n").encode("utf-8")
        return self.dual_upload(self.MANIFEST_PATH, data)

    def manifest_versions(self) -> dict[str, int | None]:
        out: dict[str, int | None] = {"quark": None, "baidu": None}
        for name, backend in (("quark", self._quark), ("baidu", self._baidu)):
            raw = backend.download(self.MANIFEST_PATH)
            if raw is None:
                continue
            try:
                out[name] = int(Manifest.from_dict(json.loads(raw.decode("utf-8"))).version)
            except (json.JSONDecodeError, KeyError, TypeError, ValueError):
                continue
        return out
