from __future__ import annotations

import hashlib
import json
from typing import Any

from niannian.storage.cloud_tier import CloudTierRouter, MANIFEST_PATH, read_tier_state
from niannian.storage.manifest import utc_now_iso


def read_mirror_state(home) -> dict[str, Any]:
    path = home / "sync" / "mirror_state.json"
    if not path.is_file():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def write_mirror_state(home, state: dict[str, Any]) -> None:
    path = home / "sync" / "mirror_state.json"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def _sha256(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def run_cloud_mirror(store, *, config: dict[str, Any] | None = None) -> dict[str, Any]:
    """每日冷备：从 active_backend 复制到 mirror_targets 中除 source 外的 configured 云。"""
    config = config or store._config
    tier_cfg = (config.get("storage") or {}).get("tier") or {}
    cloud = store._cloud

    if not isinstance(cloud, CloudTierRouter):
        return {"ok": True, "skipped_reason": "not_tiered_mode", "status": "skipped"}

    source_name = cloud.active_provider()
    source_backend = cloud.active_backend()
    role = cloud.active_role()
    now = utc_now_iso()

    if role == "onedrive_failover":
        state = {
            "last_mirror_at": now,
            "status": "skipped",
            "skipped_reason": "no_downstream_targets",
            "source": source_name,
        }
        write_mirror_state(store.home, state)
        return {"ok": True, "status": "skipped", "skipped_reason": "no_downstream_targets", "source": source_name}

    mirror_targets = list(tier_cfg.get("mirror_targets") or ["baidu", "onedrive"])
    targets = [t for t in mirror_targets if t != source_name]
    backend_map = {"baidu": cloud.baidu, "onedrive": cloud.onedrive}
    manifest = store.manifest.read()
    paths = sorted(set(manifest.files.keys()) | {MANIFEST_PATH})

    per_target_ok: dict[str, int] = {t: 0 for t in targets}
    per_target_skip: dict[str, int] = {t: 0 for t in targets}
    critical_errors: list[str] = []

    for rel_path in paths:
        data = source_backend.download(rel_path)
        if data is None:
            local_path = store._path_for_rel(rel_path) if hasattr(store, "_path_for_rel") else None
            if rel_path == MANIFEST_PATH:
                local_manifest = store.home / "sync" / "manifest.json"
                if local_manifest.is_file():
                    data = local_manifest.read_bytes()
            elif local_path is not None and local_path.is_file():
                data = local_path.read_bytes()
        if data is None:
            critical_errors.append(f"{source_name} source missing: {rel_path}")
            continue
        digest = _sha256(data)
        for target in targets:
            backend = backend_map.get(target)
            if backend is None:
                continue
            if target == "baidu" and not backend.is_configured():
                per_target_skip[target] = per_target_skip.get(target, 0) + 1
                continue
            if target == "onedrive" and not getattr(backend, "token", None):
                per_target_skip[target] = per_target_skip.get(target, 0) + 1
                continue
            existing = backend.download(rel_path)
            if existing is not None and _sha256(existing) == digest:
                per_target_skip[target] = per_target_skip.get(target, 0) + 1
                continue
            if backend.upload(rel_path, data):
                per_target_ok[target] = per_target_ok.get(target, 0) + 1
            else:
                msg = f"{target} upload failed: {rel_path}"
                critical_errors.append(msg)
                cloud.notifier.emit(
                    "mirror_failed",
                    provider=target,
                    severity="warning",
                    message=f"mirror to {target} failed: {rel_path}",
                )

    if critical_errors:
        status = "failed"
    else:
        status = "ok"

    onedrive_status = "skipped"
    if "onedrive" in targets:
        if per_target_ok.get("onedrive", 0) > 0:
            onedrive_status = "ok"
        elif critical_errors:
            onedrive_status = "failed"
        elif not cloud.onedrive.is_configured():
            onedrive_status = "skipped"

    state = {
        "last_mirror_at": now,
        "status": status,
        "source": source_name,
        "baidu_ok": per_target_ok.get("baidu", 0),
        "baidu_skipped": per_target_skip.get("baidu", 0),
        "onedrive_ok": per_target_ok.get("onedrive", 0),
        "onedrive_skipped": per_target_skip.get("onedrive", 0),
        "onedrive_status": onedrive_status,
        "onedrive_last": now if per_target_ok.get("onedrive", 0) else read_mirror_state(store.home).get("onedrive_last"),
        "critical_errors": critical_errors,
        "errors": critical_errors,
    }
    write_mirror_state(store.home, state)
    return {
        "ok": not critical_errors,
        "status": status,
        "source": source_name,
        "files_copied": sum(per_target_ok.values()),
        "targets": per_target_ok,
        "skipped_reason": state.get("skipped_reason"),
        "errors": critical_errors,
    }
