from __future__ import annotations

import json
import os
import tempfile
import threading
import time
from pathlib import Path
from typing import Any


class State:
    def __init__(self, path: Path):
        self.path = path
        self.data: dict[str, Any] = {"baselines": {}, "cooldowns": {}}
        self._lock = threading.Lock()
        self.load()

    def load(self) -> None:
        try:
            self.data.update(json.loads(self.path.read_text()))
        except FileNotFoundError:
            pass
        except (OSError, json.JSONDecodeError):
            self.data = {"baselines": {}, "cooldowns": {}}

    def save(self) -> None:
        # Concurrent writers (6 daemon threads) must not share one tmp name: a
        # shared tmp races (A replaces, B's replace hits FileNotFoundError -> crash).
        # Per-call mkstemp in the same dir + a lock -> atomic os.replace, no race.
        self.path.parent.mkdir(parents=True, exist_ok=True)
        with self._lock:
            payload = json.dumps(self.data, sort_keys=True, indent=2)
            fd, tmp = tempfile.mkstemp(dir=self.path.parent, prefix=".state-", suffix=".tmp")
            try:
                with os.fdopen(fd, "w") as handle:
                    handle.write(payload)
                    handle.flush()
                    os.fsync(handle.fileno())
                os.replace(tmp, self.path)
            except BaseException:
                try:
                    os.unlink(tmp)
                except OSError:
                    pass
                raise

    def baseline(self, key: str, default: Any) -> Any:
        return self.data.setdefault("baselines", {}).get(key, default)

    def set_baseline(self, key: str, value: Any) -> None:
        self.data.setdefault("baselines", {})[key] = value
        self.save()

    def allow_now(self, key: str, seconds: int, now: float | None = None) -> bool:
        now = now if now is not None else time.time()
        last = float(self.data.setdefault("cooldowns", {}).get(key, 0))
        if now - last < seconds:
            return False
        self.data["cooldowns"][key] = now
        self.save()
        return True
