from __future__ import annotations

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from niannian.storage.manifest import utc_now_iso

DEFAULT_OPS_ERRORS_PATH = "sync/ops_errors.jsonl"


def resolve_ops_errors_path(config: dict[str, Any] | None) -> str:
    ops = ((config or {}).get("storage") or {}).get("tier") or {}
    ops_cfg = ops.get("ops") or {}
    return str(ops_cfg.get("error_log", DEFAULT_OPS_ERRORS_PATH))


def resolve_ops_errors_max_lines(config: dict[str, Any] | None) -> int:
    ops = ((config or {}).get("storage") or {}).get("tier") or {}
    ops_cfg = ops.get("ops") or {}
    return int(ops_cfg.get("error_log_max_lines", 500))


class OpsErrorLog:
    """NAS/日常运维错误 — append-only jsonl，随 L1 三云 sync，不进 L2 archive。"""

    def __init__(self, home: Path, *, config: dict[str, Any] | None = None) -> None:
        self._home = home
        self._config = config or {}
        rel = resolve_ops_errors_path(self._config)
        self._path = home / rel
        self._max_lines = resolve_ops_errors_max_lines(self._config)

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

    @property
    def rel_path(self) -> str:
        return resolve_ops_errors_path(self._config)

    def record(
        self,
        code: str,
        *,
        message: str = "",
        severity: str = "error",
        component: str = "ops",
        node: str | None = None,
        detail: dict[str, Any] | None = None,
        check_key: str | None = None,
    ) -> dict[str, Any]:
        if check_key and self._has_open_check_key(check_key):
            return {"skipped": True, "check_key": check_key}

        row: dict[str, Any] = {
            "id": uuid.uuid4().hex[:12],
            "at": utc_now_iso(),
            "node": node or self._default_node(),
            "component": component,
            "code": code,
            "severity": severity,
            "message": message[:500] if message else code,
            "detail": detail or {},
            "resolved": False,
        }
        if check_key:
            row["check_key"] = check_key

        self._path.parent.mkdir(parents=True, exist_ok=True)
        with self._path.open("a", encoding="utf-8") as fh:
            fh.write(json.dumps(row, ensure_ascii=False) + "\n")
        self._trim()
        return row

    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():
            line = line.strip()
            if not line:
                continue
            try:
                rows.append(json.loads(line))
            except json.JSONDecodeError:
                continue
        return rows

    def recent(self, *, limit: int = 50, unresolved_only: bool = False) -> list[dict[str, Any]]:
        rows = self.read_all()
        if unresolved_only:
            rows = [r for r in rows if not r.get("resolved")]
        return rows[-limit:]

    def summary(self) -> dict[str, Any]:
        rows = self.read_all()
        open_rows = [r for r in rows if not r.get("resolved")]
        by_code: dict[str, int] = {}
        for r in open_rows:
            code = str(r.get("code") or "unknown")
            by_code[code] = by_code.get(code, 0) + 1
        return {
            "path": self.rel_path,
            "total": len(rows),
            "open": len(open_rows),
            "by_code": by_code,
            "latest_at": rows[-1].get("at") if rows else None,
        }

    def acknowledge(self, *, error_id: str | None = None, code: str | None = None) -> int:
        rows = self.read_all()
        if not rows:
            return 0
        now = utc_now_iso()
        count = 0
        for row in rows:
            if row.get("resolved"):
                continue
            if error_id and row.get("id") != error_id:
                continue
            if code and row.get("code") != code:
                continue
            if not error_id and not code:
                continue
            row["resolved"] = True
            row["resolved_at"] = now
            count += 1
        if count:
            self._rewrite(rows)
        return count

    def acknowledge_all(self) -> int:
        rows = self.read_all()
        if not rows:
            return 0
        now = utc_now_iso()
        count = 0
        for row in rows:
            if row.get("resolved"):
                continue
            row["resolved"] = True
            row["resolved_at"] = now
            count += 1
        if count:
            self._rewrite(rows)
        return count

    def _default_node(self) -> str:
        import os

        mode = os.environ.get("NIANNIAN_MODE") or (
            (self._config.get("runtime") or {}).get("mode", "auto")
        )
        return "docker-nas" if mode == "docker" else "mac"

    def _has_open_check_key(self, check_key: str) -> bool:
        for row in reversed(self.read_all()):
            if row.get("check_key") == check_key and not row.get("resolved"):
                return True
        return False

    def _trim(self) -> None:
        if self._max_lines <= 0 or not self._path.is_file():
            return
        lines = [ln for ln in self._path.read_text(encoding="utf-8").splitlines() if ln.strip()]
        if len(lines) <= self._max_lines:
            return
        kept = lines[-self._max_lines :]
        self._path.write_text("\n".join(kept) + "\n", encoding="utf-8")

    def _rewrite(self, rows: list[dict[str, Any]]) -> None:
        self._path.parent.mkdir(parents=True, exist_ok=True)
        with self._path.open("w", encoding="utf-8") as fh:
            for row in rows:
                fh.write(json.dumps(row, ensure_ascii=False) + "\n")
