"""Router tests for browser-connectable IM channels."""

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from urllib.parse import parse_qs, urlparse
from uuid import UUID

from _router_auth_helpers import make_authed_test_app
from fastapi.testclient import TestClient

from app.channels.providers.discord_connect import DiscordIdentity
from app.channels.providers.slack_connect import SlackInstall
from app.gateway.auth.models import User
from app.gateway.routers import channel_connections
from deerflow.config.channel_connections_config import ChannelConnectionsConfig


def _user() -> User:
    return User(
        id=UUID("11111111-2222-3333-4444-555555555555"),
        email="alice@example.com",
        password_hash="x",
        system_role="user",
    )


async def _make_repo(tmp_path):
    from deerflow.persistence.channel_connections import ChannelConnectionRepository, ChannelCredentialCipher
    from deerflow.persistence.engine import get_session_factory, init_engine

    await init_engine("sqlite", url=f"sqlite+aiosqlite:///{tmp_path / 'router.db'}", sqlite_dir=str(tmp_path))
    return ChannelConnectionRepository(
        get_session_factory(),
        cipher=ChannelCredentialCipher.from_key("router-secret"),
    )


def _make_app(config: ChannelConnectionsConfig, repo):
    app = make_authed_test_app(user_factory=_user)
    app.state.channel_connections_config = config
    app.state.channel_connection_repo = repo
    app.include_router(channel_connections.router)
    return app


def test_get_providers_returns_catalog_and_current_status(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)
    config = ChannelConnectionsConfig.model_validate(
        {
            "enabled": True,
            "public_base_url": "https://deerflow.example.com",
            "encryption_key": "router-secret",
            "telegram": {
                "enabled": True,
                "bot_token": "telegram-token",
                "bot_username": "deerflow_bot",
                "webhook_secret": "telegram-secret",
            },
            "slack": {"enabled": True, "client_id": "slack-client"},
        }
    )
    app = _make_app(config, repo)

    with TestClient(app) as client:
        response = client.get("/api/channels/providers")

    assert response.status_code == 200
    body = response.json()
    assert body["enabled"] is True
    telegram = next(item for item in body["providers"] if item["provider"] == "telegram")
    slack = next(item for item in body["providers"] if item["provider"] == "slack")
    assert telegram["enabled"] is True
    assert telegram["configured"] is True
    assert telegram["connection_status"] == "not_connected"
    assert slack["enabled"] is True
    assert slack["configured"] is False

    anyio.run(repo.close)


def test_get_connections_returns_current_user_connections_only(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)

    async def seed_connections():
        await repo.upsert_connection(
            owner_user_id=str(_user().id),
            provider="telegram",
            external_account_id="42",
            external_account_name="Alice",
            status="connected",
        )
        await repo.upsert_connection(
            owner_user_id="other-user",
            provider="telegram",
            external_account_id="99",
            external_account_name="Bob",
            status="connected",
        )

    anyio.run(seed_connections)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.get("/api/channels/connections")

    assert response.status_code == 200
    body = response.json()
    assert len(body["connections"]) == 1
    assert body["connections"][0]["provider"] == "telegram"
    assert body["connections"][0]["external_account_id"] == "42"

    anyio.run(repo.close)


def test_connect_telegram_returns_deep_link_and_persists_state(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
                "telegram": {
                    "enabled": True,
                    "bot_token": "telegram-token",
                    "bot_username": "deerflow_bot",
                    "webhook_secret": "telegram-secret",
                },
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.post("/api/channels/telegram/connect")

    assert response.status_code == 200
    body = response.json()
    assert body["provider"] == "telegram"
    assert body["mode"] == "deep_link"
    assert body["url"].startswith("https://t.me/deerflow_bot?start=")

    async def count_states():
        return await repo.count_oauth_states(owner_user_id=str(_user().id), provider="telegram")

    assert anyio.run(count_states) == 1

    anyio.run(repo.close)


def test_connect_unconfigured_provider_returns_400(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
                "slack": {"enabled": True, "client_id": "slack-client"},
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.post("/api/channels/slack/connect")

    assert response.status_code == 400
    assert response.json()["detail"] == "Channel provider is not configured"

    anyio.run(repo.close)


def test_connect_discord_includes_bot_install_scope_and_permissions(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
                "discord": {
                    "enabled": True,
                    "client_id": "discord-client",
                    "client_secret": "discord-secret",
                    "bot_token": "discord-bot",
                    "permissions": "274877975552",
                },
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.post("/api/channels/discord/connect")

    assert response.status_code == 200
    url = response.json()["url"]
    parsed = urlparse(url)
    query = parse_qs(parsed.query)
    scopes = set(query["scope"][0].split())
    assert {"identify", "guilds", "bot", "applications.commands"}.issubset(scopes)
    assert query["permissions"] == ["274877975552"]

    anyio.run(repo.close)


def test_slack_callback_exchanges_code_and_stores_connection(tmp_path, monkeypatch):
    import anyio

    from app.channels.providers import slack_connect

    repo = anyio.run(_make_repo, tmp_path)
    state_token = "slack-state-token"

    async def seed_state():
        await repo.create_oauth_state(
            owner_user_id=str(_user().id),
            provider="slack",
            state=state_token,
            expires_at=datetime.now(UTC) + timedelta(minutes=5),
            requested_scopes=["chat:write"],
        )

    async def fake_exchange_slack_oauth_code(**kwargs):
        assert kwargs["code"] == "slack-code"
        assert kwargs["redirect_uri"] == "https://deerflow.example.com/api/channels/slack/callback"
        return SlackInstall(
            team_id="T123",
            team_name="Deer Team",
            authed_user_id="U123",
            bot_user_id="B123",
            bot_access_token="xoxb-secret",
            scopes=["chat:write"],
            raw={"ok": True},
        )

    anyio.run(seed_state)
    monkeypatch.setattr(slack_connect, "exchange_slack_oauth_code", fake_exchange_slack_oauth_code)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
                "slack": {
                    "enabled": True,
                    "client_id": "slack-client",
                    "client_secret": "slack-secret",
                    "signing_secret": "slack-signing-secret",
                },
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.get(
            f"/api/channels/slack/callback?code=slack-code&state={state_token}",
            follow_redirects=False,
        )

    assert response.status_code in {302, 307}
    assert response.headers["location"] == "/workspace?channel_connected=slack"

    async def get_connection_and_credentials():
        connections = await repo.list_connections(str(_user().id))
        credentials = await repo.get_credentials(connections[0]["id"])
        return connections[0], credentials

    connection, credentials = anyio.run(get_connection_and_credentials)
    assert connection["provider"] == "slack"
    assert connection["external_account_id"] == "U123"
    assert connection["workspace_id"] == "T123"
    assert connection["bot_user_id"] == "B123"
    assert connection["scopes"] == ["chat:write"]
    assert credentials["access_token"] == "xoxb-secret"

    anyio.run(repo.close)


def test_discord_callback_exchanges_code_and_stores_identity(tmp_path, monkeypatch):
    import anyio

    from app.channels.providers import discord_connect

    repo = anyio.run(_make_repo, tmp_path)
    state_token = "discord-state-token"

    async def seed_state():
        await repo.create_oauth_state(
            owner_user_id=str(_user().id),
            provider="discord",
            state=state_token,
            expires_at=datetime.now(UTC) + timedelta(minutes=5),
            requested_scopes=["identify", "guilds"],
        )

    async def fake_complete_discord_oauth(**kwargs):
        assert kwargs["code"] == "discord-code"
        assert kwargs["redirect_uri"] == "https://deerflow.example.com/api/channels/discord/callback"
        return DiscordIdentity(
            user_id="987",
            display_name="Alice",
            username="alice",
            guilds=[{"id": "G1", "name": "Guild One"}],
            access_token="discord-access-token",
            refresh_token="discord-refresh-token",
            token_type="Bearer",
            scopes=["identify", "guilds"],
            expires_at=datetime.now(UTC) + timedelta(hours=1),
            raw_token={"scope": "identify guilds"},
        )

    anyio.run(seed_state)
    monkeypatch.setattr(discord_connect, "complete_discord_oauth", fake_complete_discord_oauth)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
                "discord": {
                    "enabled": True,
                    "client_id": "discord-client",
                    "client_secret": "discord-secret",
                    "bot_token": "discord-bot",
                },
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.get(
            f"/api/channels/discord/callback?code=discord-code&state={state_token}",
            follow_redirects=False,
        )

    assert response.status_code in {302, 307}
    assert response.headers["location"] == "/workspace?channel_connected=discord"

    async def get_connection_and_credentials():
        connections = await repo.list_connections(str(_user().id))
        credentials = await repo.get_credentials(connections[0]["id"])
        return connections[0], credentials

    connection, credentials = anyio.run(get_connection_and_credentials)
    assert connection["provider"] == "discord"
    assert connection["external_account_id"] == "987"
    assert connection["external_account_name"] == "Alice"
    assert connection["metadata"]["guilds"] == [{"id": "G1", "name": "Guild One"}]
    assert credentials["access_token"] == "discord-access-token"
    assert credentials["refresh_token"] == "discord-refresh-token"

    anyio.run(repo.close)


def test_disconnect_connection_revokes_current_user_connection(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)

    async def seed_connection():
        connection = await repo.upsert_connection(
            owner_user_id=str(_user().id),
            provider="telegram",
            external_account_id="42",
            status="connected",
        )
        await repo.store_credentials(connection["id"], access_token="secret-token")
        return connection["id"]

    connection_id = anyio.run(seed_connection)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.delete(f"/api/channels/connections/{connection_id}")

    assert response.status_code == 204

    async def get_connection_status():
        return (await repo.list_connections(str(_user().id)))[0]["status"]

    assert anyio.run(get_connection_status) == "revoked"
    assert anyio.run(repo.get_credentials, connection_id) is None

    anyio.run(repo.close)


def test_disconnect_connection_is_current_user_scoped(tmp_path):
    import anyio

    repo = anyio.run(_make_repo, tmp_path)

    async def seed_connection():
        connection = await repo.upsert_connection(
            owner_user_id="other-user",
            provider="telegram",
            external_account_id="42",
            status="connected",
        )
        return connection["id"]

    connection_id = anyio.run(seed_connection)
    app = _make_app(
        ChannelConnectionsConfig.model_validate(
            {
                "enabled": True,
                "public_base_url": "https://deerflow.example.com",
                "encryption_key": "router-secret",
            }
        ),
        repo,
    )

    with TestClient(app) as client:
        response = client.delete(f"/api/channels/connections/{connection_id}")

    assert response.status_code == 404

    async def get_connection_status():
        return (await repo.list_connections("other-user"))[0]["status"]

    assert anyio.run(get_connection_status) == "connected"

    anyio.run(repo.close)
