"""Periodic state snapshot for debugging and observability.

Logs the full runner state every 30 seconds. This is the one thing you can't
reconstruct after the fact — when a bug happens, traces show the request flow
but not what the runner thought the world looked like.

Usage:
    from skop_runner.state_snapshot import start_snapshot_loop
    task = start_snapshot_loop(message_handler, client, output_buffer)
"""

import asyncio
import os
import sys
import time
from typing import TYPE_CHECKING, Optional

from .structured_log import get_logger

if TYPE_CHECKING:
    from .message_handler import MessageHandler
    from .client import RunnerClient
    from .output_buffer import OutputBuffer

_log = get_logger("state_snapshot")

SNAPSHOT_INTERVAL_S = 30
_start_time: float = time.monotonic()


async def _snapshot_loop(
    message_handler: "MessageHandler",
    client: Optional["RunnerClient"],
    output_buffer: Optional["OutputBuffer"],
) -> None:
    """Emit state.snapshot every SNAPSHOT_INTERVAL_S seconds."""
    global _start_time
    _start_time = time.monotonic()

    while True:
        await asyncio.sleep(SNAPSHOT_INTERVAL_S)
        try:
            snapshot = _collect_snapshot(message_handler, client, output_buffer)
            _log.info("state.snapshot", **snapshot)
        except Exception as e:
            _log.warning("state.snapshot_failed", error=str(e))


def _collect_snapshot(
    message_handler: "MessageHandler",
    client: Optional["RunnerClient"],
    output_buffer: Optional["OutputBuffer"],
) -> dict:
    """Gather current state from all components."""
    snapshot: dict = {}

    # Workspace root
    try:
        fm = message_handler.file_manager
        snapshot["workspace_root"] = str(fm.project_root) if fm.project_root else None
    except Exception:
        snapshot["workspace_root"] = None

    # Sessions
    try:
        sm = message_handler.session_manager
        sessions = sm.list_sessions()
        snapshot["session_count"] = len(sessions)
        snapshot["kernel_states"] = {
            s["kernel_id"]: s.get("notebook_path", "unknown")
            for s in sessions
        }
    except Exception:
        snapshot["session_count"] = 0
        snapshot["kernel_states"] = {}

    # Connection status
    try:
        if client:
            snapshot["connection_status"] = "connected" if client.ws and not client.ws.closed else "disconnected"
        else:
            snapshot["connection_status"] = "unknown"
    except Exception:
        snapshot["connection_status"] = "unknown"

    # Queue depths
    try:
        eq = message_handler._execution_queue
        queue_info = eq.get_queue_state()
        snapshot["queue_depths"] = {
            kid: info.get("queued", 0) for kid, info in queue_info.items()
        }
    except Exception:
        snapshot["queue_depths"] = {}

    # Buffer size
    try:
        if output_buffer:
            snapshot["buffer_size"] = len(output_buffer._buffer)
        else:
            snapshot["buffer_size"] = 0
    except Exception:
        snapshot["buffer_size"] = 0

    # System metrics
    snapshot["uptime_s"] = round(time.monotonic() - _start_time, 1)

    try:
        import resource
        usage = resource.getrusage(resource.RUSAGE_SELF)
        if sys.platform == "darwin":
            # macOS: ru_maxrss is in bytes
            snapshot["memory_mb"] = round(usage.ru_maxrss / (1024 * 1024), 1)
        else:
            # Linux: ru_maxrss is in kilobytes
            snapshot["memory_mb"] = round(usage.ru_maxrss / 1024, 1)
    except Exception:
        snapshot["memory_mb"] = None

    snapshot["python_version"] = sys.version.split()[0]

    return snapshot


def start_snapshot_loop(
    message_handler: "MessageHandler",
    client: Optional["RunnerClient"] = None,
    output_buffer: Optional["OutputBuffer"] = None,
) -> asyncio.Task:
    """Start the periodic snapshot loop as an asyncio task."""
    return asyncio.create_task(
        _snapshot_loop(message_handler, client, output_buffer)
    )
