from __future__ import annotations

import asyncio

from beaver.foundation.config.schema import ChannelConfig
from beaver.interfaces.channels.connections import (
    ChannelConnectionStore,
    ChannelConnectorRegistry,
    ChannelRuntimeSpec,
    CredentialStore,
    ValidationResult,
)


class FakeConnector:
    kind = "fake"

    def __init__(self) -> None:
        self.validated: list[str] = []
        self.revoked: list[str] = []

    async def validate(self, connection_id: str) -> ValidationResult:
        self.validated.append(connection_id)
        return ValidationResult(ok=True, status="connected", account_id="fake-account", display_name="Fake")

    async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec:
        return ChannelRuntimeSpec(
            channel_id="fake-channel",
            kind="fake",
            mode="webhook",
            account_id="fake-account",
            display_name="Fake",
            config={"enabled": True},
        )

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


def test_connector_registry_dispatches_by_kind(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        connector = FakeConnector()
        registry = ChannelConnectorRegistry(connection_store=connection_store, credential_store=credential_store)
        registry.register(connector)

        connection = connection_store.create(
            kind="fake",
            mode="webhook",
            display_name="Fake",
            account_id="fake-account",
            owner_user_id=None,
            auth_type="token",
        )
        result = await registry.validate(connection.connection_id)
        spec = await registry.materialize_runtime(connection.connection_id)

        assert result.ok is True
        assert connector.validated == [connection.connection_id]
        assert spec.channel_id == "fake-channel"

    asyncio.run(run())


def test_connector_registry_materializes_channel_configs_with_credentials(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        credentials_ref = credential_store.put(kind="telegram", values={"botToken": "token-1"})
        connection = connection_store.create(
            kind="fake",
            mode="webhook",
            display_name="Connected",
            account_id="connected",
            owner_user_id=None,
            auth_type="token",
            credentials_ref=credentials_ref,
        )
        connection_store.update_status(connection.connection_id, status="connected", last_error=None)

        class CredentialAwareConnector(FakeConnector):
            async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec:
                stored = connection_store.get(connection_id)
                return ChannelRuntimeSpec(
                    channel_id="fake-channel",
                    kind="fake",
                    mode="webhook",
                    account_id="fake-account",
                    display_name="Fake",
                    config={"enabled": True},
                    secrets_ref=stored.credentials_ref,
                )

        registry = ChannelConnectorRegistry(connection_store=connection_store, credential_store=credential_store)
        registry.register(CredentialAwareConnector())

        configs = await registry.materialize_channel_configs()

        assert isinstance(configs["fake-channel"], ChannelConfig)
        assert configs["fake-channel"].enabled is True
        assert configs["fake-channel"].secrets == {"botToken": "token-1"}

    asyncio.run(run())


def test_connector_registry_materializes_only_connected_connections(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        registry = ChannelConnectorRegistry(connection_store=connection_store, credential_store=credential_store)
        registry.register(FakeConnector())

        draft = connection_store.create(
            kind="fake",
            mode="webhook",
            display_name="Draft",
            account_id="draft",
            owner_user_id=None,
            auth_type="token",
        )
        connected = connection_store.create(
            kind="fake",
            mode="webhook",
            display_name="Connected",
            account_id="connected",
            owner_user_id=None,
            auth_type="token",
        )
        connection_store.update_status(connected.connection_id, status="connected", last_error=None)

        specs = await registry.materialize_connected_runtime_specs()

        assert [spec.channel_id for spec in specs] == ["fake-channel"]
        assert connection_store.get(draft.connection_id).status == "draft"

    asyncio.run(run())


def test_connector_registry_revoke_calls_connector_and_updates_store(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        connector = FakeConnector()
        registry = ChannelConnectorRegistry(connection_store=connection_store, credential_store=credential_store)
        registry.register(connector)

        connection = connection_store.create(
            kind="fake",
            mode="webhook",
            display_name="Fake",
            account_id="fake-account",
            owner_user_id=None,
            auth_type="token",
        )
        connection_store.update_status(connection.connection_id, status="connected", last_error=None)

        await registry.revoke(connection.connection_id)

        assert connector.revoked == [connection.connection_id]
        assert connection_store.get(connection.connection_id).status == "revoked"

    asyncio.run(run())
