"""AD-514: Service boundary protocols for interface segregation.

These protocols define the narrow interfaces that consumers depend on,
enabling decomposition of ProbOSRuntime into focused modules.
"""

from __future__ import annotations

from typing import Any, Callable, Iterable, Sequence, TYPE_CHECKING, Protocol, runtime_checkable

from probos.types import IntentMessage, Priority  # AD-654b: concrete types for queue protocol

if TYPE_CHECKING:
    from probos.events import BaseEvent, EventType


# ── EventEmitterMixin (BF-092) ─────────────────────────────────────


class EventEmitterMixin:
    """Shared _emit() for services that hold an _emit_event callback.

    Expects the subclass ``__init__`` to set ``self._emit_event`` to a
    ``Callable[[str, dict], None] | None``.
    """

    _emit_event: Callable[..., Any] | None

    def _emit(self, event_type: str, data: dict[str, Any]) -> None:
        if self._emit_event:
            self._emit_event(event_type, data)

if TYPE_CHECKING:
    from probos.cognitive.self_mod import DesignedAgentRecord
    from probos.consensus.trust import TrustRecord
    from probos.ontology import VesselOntologyService
    from probos.types import Episode


@runtime_checkable
class EpisodicMemoryProtocol(Protocol):
    """What agents and services need from episodic memory."""

    async def store(self, episode: Episode) -> None: ...
    async def recall(self, query: str, k: int = 5) -> list[Episode]: ...
    async def start(self) -> None: ...
    async def stop(self) -> None: ...


@runtime_checkable
class TrustNetworkProtocol(Protocol):
    """What services need from the trust network."""

    def get_trust_score(self, agent_id: str) -> float: ...
    def get_or_create(self, agent_id: str) -> TrustRecord: ...
    def record_outcome(self, agent_id: str, success: bool, weight: float = 1.0) -> float: ...
    def remove_agent(self, agent_id: str) -> None: ...
    # AD-702: Diplomatic Relations — discounted transitivity surface.
    # Pre-build verification (2026-05-08): 0 mock sites for
    # TrustNetworkProtocol in tests/, so widening is safe per the >5-mocks
    # STOP rule. Defaulted args omitted from Protocol body.
    def transitive_score(
        self,
        observer: str,
        target: str,
        *,
        intent: str | None = None,
        safety_critical: bool = False,
    ) -> float | None: ...
    def chain_path(self, observer: str, target: str) -> list[str]: ...


@runtime_checkable
class EventLogProtocol(Protocol):
    """What services need from event logging."""

    async def log(self, category: str, agent_id: str, data: dict[str, Any] | None = None) -> None: ...
    async def start(self) -> None: ...
    async def stop(self) -> None: ...


@runtime_checkable
class WardRoomProtocol(Protocol):
    """What services need from the Ward Room."""

    async def create_thread(self, channel_id: str, title: str, author_id: str, **kwargs: Any) -> dict[str, Any]: ...
    async def create_post(self, thread_id: str, author_id: str, content: str) -> dict[str, Any]: ...
    async def get_thread(self, thread_id: str) -> dict[str, Any] | None: ...
    async def list_channels(self) -> list[dict[str, Any]]: ...
    def set_ontology(self, ontology: VesselOntologyService) -> None: ...


@runtime_checkable
class KnowledgeStoreProtocol(Protocol):
    """What services need from the knowledge store."""

    async def store_agent(self, record: DesignedAgentRecord, source_code: str) -> None: ...
    async def store_episode(self, episode: Episode) -> None: ...
    async def store_skill(self, name: str, source: str, descriptor: dict[str, Any]) -> None: ...
    async def store_trust_snapshot(self, data: dict[str, Any]) -> None: ...
    async def store_routing_snapshot(self, data: dict[str, Any]) -> None: ...


@runtime_checkable
class HebbianRouterProtocol(Protocol):
    """What services need from Hebbian routing."""

    def record_interaction(self, source: str, target: str, success: bool) -> float: ...
    def get_all_weights(self) -> dict[tuple[str, str, str], float]: ...
    def set_weight(self, key: tuple[str, str, str], value: float) -> None: ...
    def remove_weights_for_agent(self, agent_id: str) -> None: ...


@runtime_checkable
class EventEmitterProtocol(Protocol):
    """What modules need to emit HXI events."""

    def emit_event(self, event: BaseEvent | str | EventType, data: dict[str, Any] | None = None) -> None: ...
    def add_event_listener(
        self,
        fn: Callable[..., Any],
        event_types: Iterable[str] | None = None,
    ) -> None: ...
    def remove_event_listener(self, fn: Callable[..., Any]) -> None: ...


# ── Agent Cognitive Queue (AD-654b) ──────────────────────────────


@runtime_checkable
class AgentCognitiveQueueProtocol(Protocol):
    """Priority-ordered cognitive work queue for a single agent (AD-654b).

    Interposed between activation sources (JetStream, proactive loop)
    and agent.handle_intent(). Items are dequeued by priority:
    CRITICAL first, NORMAL next, LOW last.
    """

    def enqueue(
        self,
        intent: IntentMessage,
        priority: Priority,
        *,
        js_msg: Any | None = None,
    ) -> bool:
        """Add a work item to the queue.

        Args:
            intent: IntentMessage to process.
            priority: Priority enum value (CRITICAL/NORMAL/LOW).
            js_msg: JetStream message for ack/term semantics. None for
                    proactive-loop items (no JetStream backing).
                    Typed as Any because the concrete type is nats.aio.msg.Msg
                    and we don't want nats-py as a protocol layer dependency.

        Returns:
            True if enqueued, False if shed (queue full, lowest priority shed).
        """
        ...

    def pending_count(self) -> int:
        """Number of items currently in the queue."""
        ...

    def is_processing(self) -> bool:
        """Whether the queue processor loop is actively running a cognitive chain."""
        ...

    async def start(self) -> None:
        """Start the queue processor loop (asyncio.Task)."""
        ...

    async def shutdown(self) -> None:
        """Stop the processor loop. In-flight handler gets grace period, then cancel."""
        ...


# ── TaskEvent Dispatcher (AD-654c) ────────────────────────────────


@runtime_checkable
class DispatcherProtocol(Protocol):
    """Routes TaskEvents to agent cognitive queues (AD-654c).

    Consumers depend on this narrow interface, not the concrete
    Dispatcher class. Follows the existing Protocol pattern
    (9 existing protocols in this file).
    """

    async def dispatch(self, event: "TaskEvent") -> "DispatchResult":
        """Route a TaskEvent to the appropriate agent(s)."""
        ...


# ── Database abstraction (AD-542) ──────────────────────────────────


@runtime_checkable
class DatabaseConnection(Protocol):
    """Abstract async database connection.

    Mirrors the aiosqlite.Connection API surface used throughout ProbOS.
    Commercial overlays implement this protocol for Postgres/cloud backends.
    """

    async def execute(self, sql: str, parameters: Sequence[Any] = ...) -> Any:
        """Execute a single SQL statement."""
        ...

    async def executemany(self, sql: str, parameters: Iterable[Sequence[Any]]) -> Any:
        """Execute a SQL statement for each set of parameters."""
        ...

    async def executescript(self, sql_script: str) -> None:
        """Execute a multi-statement SQL script."""
        ...

    async def fetchone(self) -> Any:
        """Fetch the next row from the last executed query."""
        ...

    async def fetchall(self) -> Any:
        """Fetch all remaining rows from the last executed query."""
        ...

    async def commit(self) -> None:
        """Commit the current transaction."""
        ...

    async def close(self) -> None:
        """Close the connection."""
        ...


@runtime_checkable
class ConnectionFactory(Protocol):
    """Factory for creating database connections.

    Default implementation wraps aiosqlite.connect().
    Commercial overlays provide Postgres/cloud implementations.
    """

    async def connect(self, db_path: str) -> DatabaseConnection:
        """Create and return a new database connection."""
        ...


# ── NATS Event Bus (AD-637) ──────────────────────────────────────


@runtime_checkable
class NATSBusProtocol(Protocol):
    """What services need from the NATS event bus (AD-637).

    Narrow consumer-facing interface — does NOT include start/stop,
    set_subject_prefix, or ensure_stream (implementation-only).
    """

    @property
    def connected(self) -> bool: ...
    @property
    def subject_prefix(self) -> str: ...
    async def publish(self, subject: str, data: dict[str, Any], headers: dict[str, str] | None = None) -> None: ...
    async def subscribe(self, subject: str, callback: Any, queue: str = "") -> Any: ...
    async def request(self, subject: str, data: dict[str, Any], timeout: float = 5.0) -> Any: ...
    async def js_publish(self, subject: str, data: dict[str, Any], headers: dict[str, str] | None = None) -> None: ...
    async def js_subscribe(self, subject: str, callback: Any, durable: str | None = None, stream: str | None = None, max_ack_pending: int | None = None, ack_wait: int | None = None, manual_ack: bool = False, max_deliver: int | None = None) -> Any: ...
    async def delete_consumer(self, stream: str, durable_name: str) -> None: ...
    async def publish_raw(self, subject: str, data: dict[str, Any], headers: dict[str, str] | None = None) -> None: ...
    async def subscribe_raw(self, subject: str, callback: Any, queue: str = "") -> Any: ...
    def health(self) -> dict[str, Any]: ...
    def register_on_prefix_change(self, callback: Any) -> None: ...
