from __future__ import annotations

import asyncio

from fastapi.testclient import TestClient

from beaver.interfaces.channels.connections import (
    ChannelConnectionStore,
    ChannelConnectorRegistry,
    CredentialStore,
    FeishuConnector,
    WeixinConnector,
)
from beaver.interfaces.web.app import create_app
from beaver.services.agent_service import AgentService


class FakeSidecarClient:
    def __init__(self) -> None:
        self.sessions: dict[str, dict] = {}
        self.started: list[dict] = []
        self.logged_out: list[str] = []

    async def start_session(self, payload: dict) -> dict:
        self.started.append(payload)
        session = {
            "sessionId": "cs_1",
            "kind": payload["kind"],
            "status": "qr_ready",
            "qrImage": "data:image/png;base64,abc",
            "accountId": None,
            "displayName": None,
            "metadata": {},
        }
        self.sessions["cs_1"] = session
        return session

    async def get_session(self, session_id: str) -> dict:
        return self.sessions[session_id]

    async def logout(self, connection_id: str) -> dict:
        self.logged_out.append(connection_id)
        return {"ok": True}


def test_weixin_connector_starts_connector_session(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        client = FakeSidecarClient()
        connector = WeixinConnector(
            connection_store=connection_store,
            credential_store=credential_store,
            sidecar_client=client,
            sidecar_base_url="http://external-connector:8787",
        )

        view = await connector.start_session(display_name="Weixin Main", owner_user_id="user-1", options={})

        assert view["sessionId"] == "cs_1"
        assert view["connectionId"].startswith("conn_")
        assert client.started[0]["kind"] == "weixin"
        assert client.started[0]["connectionId"].startswith("conn_")
        assert connection_store.list()[0].kind == "weixin"
        assert connection_store.list()[0].status == "pairing"

    asyncio.run(run())


def test_weixin_connector_poll_connected_materializes_external_runtime(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        client = FakeSidecarClient()
        connector = WeixinConnector(
            connection_store=connection_store,
            credential_store=credential_store,
            sidecar_client=client,
            sidecar_base_url="http://external-connector:8787",
        )
        await connector.start_session(display_name="Weixin Main", owner_user_id=None, options={})
        connection = connection_store.list()[0]
        client.sessions["cs_1"] = {
            "sessionId": "cs_1",
            "kind": "weixin",
            "status": "connected",
            "accountId": "weixin:me",
            "displayName": "Me",
            "metadata": {"stateRef": "state-1"},
        }

        result = await connector.poll_session("cs_1")
        updated = connection_store.get(connection.connection_id)
        spec = await connector.materialize_runtime(connection.connection_id)

        assert result["status"] == "connected"
        assert updated.status == "connected"
        assert updated.account_id == "weixin:me"
        assert spec.kind == "external_connector"
        assert spec.mode == "http"
        assert spec.config["platformKind"] == "weixin"
        assert spec.config["sidecarBaseUrl"] == "http://external-connector:8787"

    asyncio.run(run())


def test_feishu_connector_uses_feishu_kind(tmp_path) -> None:
    async def run() -> None:
        connection_store = ChannelConnectionStore(tmp_path / "connections.json")
        credential_store = CredentialStore(tmp_path / "credentials.json")
        client = FakeSidecarClient()
        connector = FeishuConnector(
            connection_store=connection_store,
            credential_store=credential_store,
            sidecar_client=client,
            sidecar_base_url="http://external-connector:8787",
        )

        await connector.start_session(display_name="Feishu Main", owner_user_id=None, options={"domain": "feishu"})

        assert client.started[0]["kind"] == "feishu"
        assert client.started[0]["options"] == {"domain": "feishu"}

    asyncio.run(run())


def test_connector_session_api_starts_and_polls_connected_session(tmp_path, monkeypatch) -> None:
    monkeypatch.setenv("EXTERNAL_CONNECTOR_TOKEN", "connector-token")
    config_path = tmp_path / "config.json"
    config_path.write_text(
        '{"agents": {"defaults": {"workspace": "%s"}}, "providers": {}}' % str(tmp_path),
        encoding="utf-8",
    )
    service = AgentService(config_path=config_path)
    app = create_app(service=service, manage_service_lifecycle=False)
    client = FakeSidecarClient()

    try:
        with TestClient(app) as http:
            state_dir = tmp_path / "state" / "channel_connections"
            connection_store = ChannelConnectionStore(state_dir / "connections.json")
            credential_store = CredentialStore(state_dir / "credentials.json")
            registry = ChannelConnectorRegistry(connection_store=connection_store, credential_store=credential_store)
            registry.register(
                WeixinConnector(
                    connection_store=connection_store,
                    credential_store=credential_store,
                    sidecar_client=client,
                    sidecar_base_url="http://external-connector:8787",
                )
            )
            app.state.channel_connector_registry = registry

            started = http.post(
                "/api/channel-connector-sessions",
                json={"kind": "weixin", "displayName": "Weixin Main", "options": {}},
            )
            session_id = started.json()["session"]["sessionId"]
            connection_id = started.json()["connection"]["connection_id"]
            client.sessions[session_id] = {
                "sessionId": session_id,
                "kind": "weixin",
                "status": "connected",
                "accountId": "weixin:me",
                "displayName": "Me",
                "metadata": {},
            }
            polled = http.get(f"/api/channel-connector-sessions/{session_id}")

            assert started.status_code == 200
            assert polled.status_code == 200
            assert polled.json()["connection"]["status"] == "connected"
            assert connection_store.get(connection_id).status == "connected"
            assert polled.json()["connection"]["channel_id"] in app.state.channel_runtime.adapters
    finally:
        service.close()
