from __future__ import annotations

import hashlib
import json

from niannian.storage.manifest import utc_now_iso

PING_REL = "sync/_roundtrip/ping.txt"
MANIFEST_REL = "sync/manifest.json"
LARGE_REL = "sync/_roundtrip/large.bin"
PING_PAYLOAD = b"niannian-roundtrip-v0.2.8\n"


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


def minimal_manifest_bytes() -> bytes:
    payload = {
        "version": 1,
        "updated_at": utc_now_iso(),
        "files": {},
        "nodes": {},
    }
    return (json.dumps(payload, ensure_ascii=False) + "\n").encode("utf-8")


def physical_path_for(provider: str, rel_path: str, remote_root: str) -> str:
    if provider == "baidu":
        from niannian.storage.baidu_pcs import pcs_path

        return pcs_path(rel_path, remote_root)
    if provider == "feimao":
        from niannian.storage.feimao_api import feimao_object_path

        return feimao_object_path(rel_path, remote_root)
    return f"{remote_root}/{rel_path}"


def probe_payload(size_bytes: int) -> bytes:
    """Deterministic probe bytes for large-file roundtrip sha256 checks."""
    if size_bytes <= 0:
        raise ValueError("probe size must be positive")
    return bytes(i % 256 for i in range(size_bytes))


def roundtrip_provider(
    backend: object,
    provider: str,
    remote_root: str,
    *,
    probe_size_mb: float | None = None,
) -> dict[str, object]:
    ping_sha = sha256_bytes(PING_PAYLOAD)
    manifest = minimal_manifest_bytes()
    manifest_sha = sha256_bytes(manifest)

    if not backend.upload(PING_REL, PING_PAYLOAD):
        raise RuntimeError(f"{provider}: ping upload failed")
    if getattr(backend, "last_upload_degraded", False):
        raise RuntimeError(f"{provider}: live upload degraded to mirror (last_upload_degraded)")
    if not backend.upload(MANIFEST_REL, manifest):
        raise RuntimeError(f"{provider}: manifest upload failed")
    if getattr(backend, "last_upload_degraded", False):
        raise RuntimeError(f"{provider}: manifest upload degraded to mirror (last_upload_degraded)")

    ping_down = backend.download(PING_REL)
    manifest_down = backend.download(MANIFEST_REL)
    if ping_down is None or sha256_bytes(ping_down) != ping_sha:
        raise RuntimeError(f"{provider}: ping download sha mismatch")
    if manifest_down is None or sha256_bytes(manifest_down) != manifest_sha:
        raise RuntimeError(f"{provider}: manifest download sha mismatch")

    result: dict[str, object] = {
        "provider": provider,
        "remote_root": remote_root,
        "ping_path": physical_path_for(provider, PING_REL, remote_root),
        "manifest_path": physical_path_for(provider, MANIFEST_REL, remote_root),
        "ping_sha256": ping_sha,
        "manifest_sha256": manifest_sha,
        "live_mode": getattr(backend, "primary_mode", lambda: "unknown")(),
        "last_upload_degraded": bool(getattr(backend, "last_upload_degraded", False)),
        "ok": True,
    }

    if probe_size_mb is not None:
        probe_bytes = int(probe_size_mb * 1024 * 1024)
        if probe_bytes <= 0:
            raise ValueError("probe_size_mb must be positive")
        large = probe_payload(probe_bytes)
        large_sha = sha256_bytes(large)
        if not backend.upload(LARGE_REL, large):
            raise RuntimeError(f"{provider}: large probe upload failed")
        if getattr(backend, "last_upload_degraded", False):
            raise RuntimeError(f"{provider}: large probe degraded to mirror (last_upload_degraded)")
        large_down = backend.download(LARGE_REL)
        if large_down is None or sha256_bytes(large_down) != large_sha:
            raise RuntimeError(f"{provider}: large probe download sha mismatch")
        result["large_probe_mb"] = probe_size_mb
        result["large_bytes"] = probe_bytes
        result["large_path"] = physical_path_for(provider, LARGE_REL, remote_root)
        result["large_sha256"] = large_sha

    return result
