"""
Session-scoped staging database for Scenario Builder Test runs.

Test executions write here instead of ~/.emds/results.db or workspace
provenance tables. The store is wiped when the Scenario Builder session ends
(cancel, apply, or close). Full Apply still re-runs through the canonical path.
"""

from __future__ import annotations

import time
import uuid
from pathlib import Path
from typing import Optional

from . import debug_logger
from .results_manager import ResultsManager

# Session DBs older than this are considered abandoned (the owning Scenario
# Builder session crashed or was force-killed before clear() ran) and are swept
# on the next store init. Kept well above any plausible session length so a
# concurrently-open session's DB is never deleted out from under it.
_STALE_SESSION_SECONDS = 24 * 60 * 60


class StagingResultsStore:
    """One SQLite file per Scenario Builder session under ~/.emds/sessions/."""

    def __init__(self, session_id: Optional[str] = None) -> None:
        self.session_id = str(session_id or uuid.uuid4())
        self._sessions_root = Path.home() / ".emds" / "sessions"
        self._sessions_root.mkdir(parents=True, exist_ok=True)
        self.db_path = self._sessions_root / f"{self.session_id}.db"
        self._sweep_stale_sessions()
        self._manager = ResultsManager(str(self.db_path))

    @property
    def results_manager(self) -> ResultsManager:
        return self._manager

    def _sweep_stale_sessions(self) -> None:
        """Best-effort removal of abandoned session DBs from prior runs.

        Only files older than ``_STALE_SESSION_SECONDS`` are removed, so an
        active session running in parallel is never touched. On Windows an
        in-use file cannot be unlinked, which the per-file guard tolerates.
        """
        try:
            now = time.time()
            for path in self._sessions_root.glob("*.db*"):
                if path == self.db_path:
                    continue
                try:
                    if now - path.stat().st_mtime > _STALE_SESSION_SECONDS:
                        path.unlink()
                except OSError:
                    # Locked (active elsewhere) or already gone — leave it.
                    continue
        except Exception as exc:  # never let cleanup break a Test run
            debug_logger.warning(
                "Staging session sweep failed",
                {"sessions_root": str(self._sessions_root), "exception": str(exc)},
            )

    def clear(self) -> None:
        """Remove the session database file (discard staging)."""
        for suffix in ("", "-wal", "-shm"):
            candidate = self.db_path if not suffix else self.db_path.with_name(self.db_path.name + suffix)
            try:
                if candidate.exists():
                    candidate.unlink()
            except OSError as exc:
                # Surface (don't silently swallow) so an orphaned staging DB is
                # diagnosable rather than silently accumulating on disk.
                debug_logger.warning(
                    "Could not delete staging session file",
                    {"path": str(candidate), "exception": str(exc)},
                )

    def __repr__(self) -> str:
        return f"StagingResultsStore(session_id={self.session_id!r}, db_path={self.db_path!r})"
