from __future__ import annotations

import base64
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from niannian.storage.manifest import utc_now_iso

PENDING_UPLOADS_PATH = "sync/pending_uploads.jsonl"


@dataclass
class DrainReport:
    ok: bool
    drained: int = 0
    errors: list[str] | None = None

    def to_dict(self) -> dict[str, Any]:
        return {"ok": self.ok, "drained": self.drained, "errors": self.errors or []}


class PendingUploadQueue:
    """L0 写队列 — local_degraded 时 append，恢复后 drain。"""

    def __init__(self, home: Path) -> None:
        self._home = home
        self._path = home / PENDING_UPLOADS_PATH

    @property
    def path(self) -> Path:
        return self._path

    def pending_count(self) -> int:
        return len(self.read_all())

    def enqueue(self, rel_path: str, data: bytes) -> None:
        self._path.parent.mkdir(parents=True, exist_ok=True)
        row = {
            "rel_path": rel_path,
            "data_b64": base64.b64encode(data).decode("ascii"),
            "at": utc_now_iso(),
        }
        with self._path.open("a", encoding="utf-8") as fh:
            fh.write(json.dumps(row, ensure_ascii=False) + "\n")

    def read_all(self) -> list[dict[str, Any]]:
        if not self._path.is_file():
            return []
        rows: list[dict[str, Any]] = []
        for line in self._path.read_text(encoding="utf-8").splitlines():
            if not line.strip():
                continue
            try:
                rows.append(json.loads(line))
            except json.JSONDecodeError:
                continue
        return rows

    def clear(self) -> None:
        if self._path.is_file():
            self._path.unlink()

    def drain(self, router: Any) -> DrainReport:
        rows = self.read_all()
        if not rows:
            return DrainReport(ok=True, drained=0)
        errors: list[str] = []
        drained = 0
        router._draining = True
        try:
            for row in rows:
                rel_path = str(row.get("rel_path", ""))
                raw_b64 = row.get("data_b64")
                if not rel_path or not raw_b64:
                    continue
                try:
                    data = base64.b64decode(str(raw_b64))
                except (ValueError, TypeError):
                    errors.append(f"invalid queue row: {rel_path}")
                    continue
                result = router.upload(rel_path, data, skip_health=True)
                if result.ok:
                    drained += 1
                else:
                    errors.append(f"drain failed: {rel_path}")
        finally:
            router._draining = False
        if not errors:
            self.clear()
        return DrainReport(ok=not errors, drained=drained, errors=errors)
