"""Connector units: Slack, HTTP, Email, and the registry/catalog."""

from __future__ import annotations

import uuid

import httpx
import pytest
from legate.agent.tooling import ToolContext
from legate.config import get_settings
from legate.connectors.base import ConnectorConfigError
from legate.connectors.email import EmailConnector
from legate.connectors.http import HttpConnector
from legate.connectors.registry import catalog_tool_specs, get_connector, known_tool_names
from legate.connectors.slack import SlackConnector


def _ctx() -> ToolContext:
    return ToolContext(workspace_id=uuid.uuid4(), settings=get_settings())


def _slack_handler(request: httpx.Request) -> httpx.Response:
    path = request.url.path
    if path.endswith("/auth.test"):
        return httpx.Response(200, json={"ok": True, "user": "legate-bot"})
    if path.endswith("/chat.postMessage"):
        return httpx.Response(200, json={"ok": True, "ts": "1700000000.000100", "channel": "C1"})
    if path.endswith("/conversations.list"):
        return httpx.Response(200, json={"ok": True, "channels": [{"id": "C1", "name": "general"}]})
    return httpx.Response(404, json={"ok": False, "error": "unknown_method"})


async def test_slack_post_message_and_test_connection(set_connector_http):
    set_connector_http(_slack_handler)
    conn = SlackConnector()

    check = await conn.test_connection({}, {"bot_token": "xoxb-x"})
    assert check.ok and "legate-bot" in check.message

    res = await conn.execute(
        "slack.post_message", {"channel": "C1", "text": "hi"}, {}, {"bot_token": "xoxb-x"}, _ctx()
    )
    assert res.ok and res.data["ts"] == "1700000000.000100"


async def test_slack_list_channels_and_default_channel(set_connector_http):
    set_connector_http(_slack_handler)
    conn = SlackConnector()

    res = await conn.execute("slack.list_channels", {}, {}, {"bot_token": "x"}, _ctx())
    assert res.ok and res.data["channels"][0]["name"] == "general"

    # channel falls back to the connector's configured default.
    posted = await conn.execute(
        "slack.post_message", {"text": "yo"}, {"default_channel": "C1"}, {"bot_token": "x"}, _ctx()
    )
    assert posted.ok

    missing = await conn.execute(
        "slack.post_message", {"text": "yo"}, {}, {"bot_token": "x"}, _ctx()
    )
    assert not missing.ok and "channel" in missing.error


async def test_http_connector_call_and_auth(set_connector_http):
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["auth"] = request.headers.get("authorization")
        captured["method"] = request.method
        return httpx.Response(
            201, json={"created": True}, headers={"content-type": "application/json"}
        )

    set_connector_http(handler)
    conn = HttpConnector()
    res = await conn.execute(
        "http.call",
        {"method": "POST", "url": "/widgets", "json": {"a": 1}},
        {"base_url": "https://api.example.com", "auth_type": "bearer"},
        {"token": "secret-token"},
        _ctx(),
    )
    assert res.ok and res.data["status_code"] == 201
    assert captured["auth"] == "Bearer secret-token"
    assert captured["method"] == "POST"


def test_http_connector_validates_config():
    conn = HttpConnector()
    conn.validate_config({"auth_type": "bearer"})  # ok
    with pytest.raises(ConnectorConfigError):
        conn.validate_config({"auth_type": "header"})  # header_name missing
    with pytest.raises(ConnectorConfigError):
        conn.validate_config({"auth_type": "weird"})


async def test_email_draft_send_and_search():
    conn = EmailConnector()
    config = {"smtp_host": "smtp.example.com", "username": "me@example.com"}
    secrets = {"password": "pw"}

    draft = await conn.execute(
        "email.draft", {"to": "a@b.com", "subject": "Hi", "body": "Hello"}, config, secrets, _ctx()
    )
    assert draft.ok and "Subject: Hi" in draft.data["mime"]

    sent = {}
    conn._smtp_send = lambda cfg, sec, msg: sent.update({"to": msg["To"], "user": cfg["username"]})
    res = await conn.execute(
        "email.send", {"to": "a@b.com", "subject": "Hi", "body": "Hello"}, config, secrets, _ctx()
    )
    assert res.ok and res.data["sent"] is True and sent["to"] == "a@b.com"

    conn._imap_search = lambda cfg, sec, mailbox, limit: [{"id": "1", "headers": "Subject: x"}]
    found = await conn.execute("email.search", {"limit": 5}, config, secrets, _ctx())
    assert found.ok and found.data["messages"][0]["id"] == "1"


async def test_email_test_connection_uses_login_seam():
    conn = EmailConnector()
    conn._smtp_login = lambda cfg, sec: None
    ok = await conn.test_connection({"smtp_host": "h", "username": "u"}, {"password": "p"})
    assert ok.ok

    def _boom(cfg, sec):
        raise OSError("connection refused")

    conn._smtp_login = _boom
    bad = await conn.test_connection({"smtp_host": "h", "username": "u"}, {"password": "p"})
    assert not bad.ok and "failed" in bad.message


def test_registry_catalog_and_known_tool_names():
    assert get_connector("slack") is not None
    assert get_connector("nope") is None

    catalog_names = {s.name for s in catalog_tool_specs()}
    assert {"slack.post_message", "http.call", "email.send"} <= catalog_names

    known = known_tool_names()
    assert {"kb.search", "http.request", "slack.post_message"} <= known
