from __future__ import annotations

from dataclasses import dataclass
import json
from pathlib import Path
import time


class NonceManager:
    def __init__(self, path: str | Path, *, signer: str) -> None:
        self.path = Path(path)
        self.signer = signer.lower()
        self.path.parent.mkdir(parents=True, exist_ok=True)

    def _read(self) -> dict:
        try:
            return json.loads(self.path.read_text(encoding="utf-8"))
        except FileNotFoundError:
            return {}
        except json.JSONDecodeError:
            return {}

    def next_nonce(self) -> int:
        lock = self.path.with_suffix(self.path.suffix + ".lock")
        if lock.exists():
            # stale lock cleanup; single-process tests should not hit this unless interrupted
            if time.time() - lock.stat().st_mtime > 30:
                lock.unlink(missing_ok=True)
        lock.write_text(str(time.time()), encoding="utf-8")
        try:
            data = self._read()
            current = int(data.get(self.signer, int(time.time() * 1000)))
            candidate = max(current + 1, int(time.time() * 1000))
            data[self.signer] = candidate
            tmp = self.path.with_suffix(self.path.suffix + ".tmp")
            tmp.write_text(json.dumps(data, sort_keys=True), encoding="utf-8")
            tmp.replace(self.path)
            return candidate
        finally:
            lock.unlink(missing_ok=True)


@dataclass(frozen=True)
class SignerLockResult:
    acquired: bool
    reason: str
    path: Path


class SignerLockRegistry:
    def __init__(self, lock_dir: str | Path) -> None:
        self.lock_dir = Path(lock_dir)
        self.lock_dir.mkdir(parents=True, exist_ok=True)

    def try_acquire(self, signer: str, *, process_id: str) -> SignerLockResult:
        safe = signer.lower().replace("/", "_")
        path = self.lock_dir / f"{safe}.lock"
        if path.exists():
            existing = path.read_text(encoding="utf-8", errors="replace").strip()
            if existing != process_id:
                return SignerLockResult(False, "signer_collision", path)
        path.write_text(process_id, encoding="utf-8")
        return SignerLockResult(True, "acquired", path)
