"""Base gateway channel abstractions."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Awaitable, Callable
import uuid


def _new_id() -> str:
    return uuid.uuid4().hex


def _now_utc() -> str:
    return datetime.now(timezone.utc).isoformat()


@dataclass(slots=True)
class InboundChannelMessage:
    """Normalized channel event."""

    channel: str
    chat_id: str
    sender_id: str
    body: str
    reply_target: str
    reply_target_type: str
    raw_body: str | None = None
    run_id: str = field(default_factory=_new_id)
    message_id: str = field(default_factory=_new_id)
    received_at: str = field(default_factory=_now_utc)
    metadata: dict[str, Any] = field(default_factory=dict)
    resolved_session_id: str | None = None

    @property
    def session_id(self) -> str:
        if self.resolved_session_id:
            return self.resolved_session_id
        return f"{self.channel}:{self.chat_id}"


InboundHandler = Callable[[InboundChannelMessage], Awaitable[None]]


class BaseChannel(ABC):
    """Abstract chat channel used by the standalone gateway."""

    name: str = "base"

    def __init__(self, config: Any, inbound_handler: InboundHandler) -> None:
        self.config = config
        self._inbound_handler = inbound_handler
        self._running = False
        self._replying_sessions: set[str] = set()

    @abstractmethod
    async def start(self) -> None:
        """Start listening for inbound messages."""

    @abstractmethod
    async def stop(self) -> None:
        """Stop the channel."""

    @abstractmethod
    async def send_text(
        self,
        reply_target: str,
        content: str,
        *,
        reply_target_type: str,
        metadata: dict[str, Any] | None = None,
    ) -> None:
        """Send plain text back to the channel."""

    async def emit(self, message: InboundChannelMessage) -> None:
        """Forward an inbound channel message to the gateway runtime."""
        await self._inbound_handler(message)

    @property
    def capabilities(self) -> tuple[str, ...]:
        """Return channel-specific runtime capabilities."""
        return ()

    async def set_reply_status(
        self,
        message: InboundChannelMessage,
        *,
        active: bool,
    ) -> None:
        """Track whether the channel is currently replying in one session."""
        if active:
            self._replying_sessions.add(message.session_id)
            return
        self._replying_sessions.discard(message.session_id)

    async def start_replying_indicator(self, message: InboundChannelMessage) -> None:
        """Best-effort hook to show that the bot is replying."""
        await self.set_reply_status(message, active=True)

    async def stop_replying_indicator(self, message: InboundChannelMessage) -> None:
        """Best-effort hook to clear the replying indicator."""
        await self.set_reply_status(message, active=False)

    @property
    def is_running(self) -> bool:
        """Return whether the channel is active."""
        return self._running

    @property
    def replying_sessions(self) -> tuple[str, ...]:
        """Return the sessions currently marked as replying."""
        return tuple(sorted(self._replying_sessions))
