"""Channel connector registry."""

from __future__ import annotations

from typing import Protocol

from beaver.foundation.config.schema import ChannelConfig

from .models import ChannelRuntimeSpec, ValidationResult
from .store import ChannelConnectionStore, CredentialStore


class ChannelConnector(Protocol):
    kind: str

    async def validate(self, connection_id: str) -> ValidationResult:
        ...

    async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec:
        ...

    async def revoke(self, connection_id: str) -> None:
        ...


class ChannelConnectorRegistry:
    def __init__(self, *, connection_store: ChannelConnectionStore, credential_store: CredentialStore) -> None:
        self.connection_store = connection_store
        self.credential_store = credential_store
        self._connectors: dict[str, ChannelConnector] = {}

    def register(self, connector: ChannelConnector) -> None:
        kind = connector.kind.strip()
        if not kind:
            raise ValueError("Connector kind is required")
        if kind in self._connectors:
            raise ValueError(f"Connector already registered: {kind}")
        self._connectors[kind] = connector

    def connectors(self) -> list[dict[str, str]]:
        return [{"kind": kind} for kind in sorted(self._connectors)]

    def connector_for_kind(self, kind: str) -> ChannelConnector:
        return self._connector(kind)

    async def validate(self, connection_id: str) -> ValidationResult:
        connection = self.connection_store.get(connection_id)
        connector = self._connector(connection.kind)
        result = await connector.validate(connection_id)
        self.connection_store.update_status(
            connection_id,
            status=result.status,
            last_error=result.error,
        )
        return result

    async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec:
        connection = self.connection_store.get(connection_id)
        return await self._connector(connection.kind).materialize_runtime(connection_id)

    async def materialize_connected_runtime_specs(self) -> list[ChannelRuntimeSpec]:
        specs: list[ChannelRuntimeSpec] = []
        for connection in self.connection_store.list():
            if connection.status not in {"connected", "running"}:
                continue
            specs.append(await self._connector(connection.kind).materialize_runtime(connection.connection_id))
        return specs

    async def materialize_channel_configs(self) -> dict[str, ChannelConfig]:
        channels: dict[str, ChannelConfig] = {}
        for spec in await self.materialize_connected_runtime_specs():
            secrets = self.credential_store.get(spec.secrets_ref) if spec.secrets_ref else {}
            channels[spec.channel_id] = ChannelConfig(
                enabled=True,
                kind=spec.kind,
                mode=spec.mode,
                account_id=spec.account_id,
                display_name=spec.display_name,
                config=dict(spec.config),
                secrets=secrets,
            )
        return channels

    async def revoke(self, connection_id: str) -> None:
        connection = self.connection_store.get(connection_id)
        await self._connector(connection.kind).revoke(connection_id)
        self.connection_store.revoke(connection_id)

    def _connector(self, kind: str) -> ChannelConnector:
        connector = self._connectors.get(kind)
        if connector is None:
            raise KeyError(f"Connector not registered: {kind}")
        return connector
