"""
MeshNode — async context manager for a Meshpocalypse mesh connection.

Wraps a NATS connection and exposes higher-level clients for SPARQL and ATF
operations. Must be used as an async context manager; calling ``publish`` or
accessing ``sparql``/``atf`` outside the context raises ``RuntimeError``.

Example::

    async with MeshNode("nats://localhost:4222") as mesh:
        await mesh.publish("my.subject", b"payload")
        rows = await mesh.sparql.query("SELECT ?s WHERE { ?s a mesh:Agent }")
        level = await mesh.atf.get_trust_level("did:mesh:my-agent")
"""

from __future__ import annotations

import nats
from nats.aio.client import Client

from .sparql import SparqlClient
from .atf import ATFClient


class MeshNode:
    """Async context manager for a Meshpocalypse mesh connection.

    Connects to NATS on ``__aenter__`` and drains gracefully on ``__aexit__``.
    Do **not** create tasks or start I/O in ``__init__``; use the async context
    manager pattern exclusively.

    Args:
        nats_url: NATS server URL. Defaults to ``"nats://localhost:4222"``.

    Raises:
        RuntimeError: If ``publish`` is called outside the ``async with`` block.
    """

    def __init__(self, nats_url: str = "nats://localhost:4222") -> None:
        self._nats_url = nats_url
        self._nc: Client | None = None
        self._sparql: SparqlClient | None = None
        self._atf: ATFClient | None = None

    async def __aenter__(self) -> "MeshNode":
        """Connect to NATS and initialise sub-clients."""
        self._nc = await nats.connect(self._nats_url)
        self._sparql = SparqlClient(self._nc)
        self._atf = ATFClient(self._nc)
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: object,
    ) -> None:
        """Drain the NATS connection and release resources."""
        if self._nc is not None:
            await self._nc.drain()
            self._nc = None
            self._sparql = None
            self._atf = None

    @property
    def sparql(self) -> SparqlClient:
        """SPARQL client for querying and updating the mesh RDF graph.

        Raises:
            RuntimeError: If accessed outside the ``async with`` block.
        """
        if self._sparql is None:
            raise RuntimeError("MeshNode not connected — use 'async with MeshNode() as mesh:'")
        return self._sparql

    @property
    def atf(self) -> ATFClient:
        """ATF trust-level client.

        Raises:
            RuntimeError: If accessed outside the ``async with`` block.
        """
        if self._atf is None:
            raise RuntimeError("MeshNode not connected — use 'async with MeshNode() as mesh:'")
        return self._atf

    async def publish(self, subject: str, payload: bytes) -> None:
        """Publish a raw bytes payload to a NATS subject.

        Args:
            subject: NATS subject string (e.g. ``"mesh.events.agent-1"``).
            payload: Raw bytes to publish.

        Raises:
            RuntimeError: If called outside the ``async with`` block.
            ValueError: If ``subject`` is empty.
        """
        if self._nc is None:
            raise RuntimeError("MeshNode not connected — use 'async with MeshNode() as mesh:'")
        if not subject:
            raise ValueError("publish: subject must be a non-empty string")
        await self._nc.publish(subject, payload)
