"""Units for the GitHub, Notion, and Webhook connectors."""

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.github import GitHubConnector
from legate.connectors.notion import NotionConnector
from legate.connectors.registry import catalog_tool_specs, get_connector, known_tool_names
from legate.connectors.webhook import WebhookConnector


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


# --- GitHub -------------------------------------------------------------------


def _github_handler(captured: dict):
    def handler(request: httpx.Request) -> httpx.Response:
        captured["auth"] = request.headers.get("authorization")
        captured["method"] = request.method
        captured["path"] = request.url.path
        if request.url.path.endswith("/user"):
            return httpx.Response(200, json={"login": "octocat"})
        if request.method == "POST" and request.url.path.endswith("/comments"):
            return httpx.Response(201, json={"id": 42, "html_url": "https://gh/c/42"})
        if request.method == "POST":  # create issue
            return httpx.Response(
                201,
                json={"number": 7, "title": "Bug", "state": "open", "html_url": "https://gh/i/7"},
            )
        if request.url.path.endswith("/issues"):  # list
            return httpx.Response(
                200, json=[{"number": 1, "title": "A", "state": "open", "html_url": "u"}]
            )
        return httpx.Response(
            200, json={"number": 3, "title": "One", "state": "open", "html_url": "u3"}
        )

    return handler


async def test_github_create_issue_signs_and_summarizes(set_connector_http):
    captured: dict = {}
    set_connector_http(_github_handler(captured))
    conn = GitHubConnector()
    res = await conn.execute(
        "github.create_issue",
        {"title": "Bug", "body": "broken", "labels": ["bug"]},
        {"default_repo": "acme/app"},
        {"token": "ghp_x"},
        _ctx(),
    )
    assert res.ok and res.data["number"] == 7 and res.data["url"] == "https://gh/i/7"
    assert captured["auth"] == "Bearer ghp_x"
    assert captured["path"] == "/repos/acme/app/issues"


async def test_github_get_and_list_issues(set_connector_http):
    set_connector_http(_github_handler({}))
    conn = GitHubConnector()
    got = await conn.execute(
        "github.get_issue", {"number": 3}, {"default_repo": "a/b"}, {"token": "t"}, _ctx()
    )
    assert got.ok and got.data["number"] == 3
    listed = await conn.execute(
        "github.list_issues", {"state": "open"}, {"default_repo": "a/b"}, {"token": "t"}, _ctx()
    )
    assert listed.ok and listed.data["issues"][0]["number"] == 1


async def test_github_comment_and_missing_repo(set_connector_http):
    captured: dict = {}
    set_connector_http(_github_handler(captured))
    conn = GitHubConnector()
    commented = await conn.execute(
        "github.comment_issue",
        {"number": 7, "body": "hi"},
        {"default_repo": "a/b"},
        {"token": "t"},
        _ctx(),
    )
    assert commented.ok and commented.data["id"] == 42

    missing = await conn.execute("github.create_issue", {"title": "x"}, {}, {"token": "t"}, _ctx())
    assert not missing.ok and "repo" in missing.error


async def test_github_test_connection(set_connector_http):
    set_connector_http(_github_handler({}))
    conn = GitHubConnector()
    check = await conn.test_connection({}, {"token": "t"})
    assert check.ok and "octocat" in check.message


# --- Notion -------------------------------------------------------------------


def _notion_handler(captured: dict):
    def handler(request: httpx.Request) -> httpx.Response:
        captured["path"] = request.url.path
        captured["version"] = request.headers.get("notion-version")
        if request.url.path.endswith("/users/me"):
            return httpx.Response(200, json={"bot": {}})
        if request.url.path.endswith("/search"):
            return httpx.Response(
                200, json={"results": [{"id": "p1", "object": "page", "url": "u"}]}
            )
        if request.url.path.endswith("/query"):
            return httpx.Response(200, json={"results": [{"id": "r1", "url": "u1"}]})
        if request.url.path.endswith("/pages"):
            return httpx.Response(200, json={"id": "new-page", "url": "https://notion/new"})
        if "/blocks/" in request.url.path:
            return httpx.Response(200, json={"results": []})
        return httpx.Response(404, json={})

    return handler


async def test_notion_create_page_uses_title_property(set_connector_http):
    captured: dict = {}
    set_connector_http(_notion_handler(captured))
    conn = NotionConnector()
    res = await conn.execute(
        "notion.create_page",
        {"title": "Task", "content": "details"},
        {"default_database_id": "db1", "title_property": "Name"},
        {"token": "secret_x"},
        _ctx(),
    )
    assert res.ok and res.data["id"] == "new-page"
    assert captured["path"].endswith("/pages")
    assert captured["version"] == "2022-06-28"


async def test_notion_query_search_and_append(set_connector_http):
    set_connector_http(_notion_handler({}))
    conn = NotionConnector()
    q = await conn.execute(
        "notion.query_database", {}, {"default_database_id": "db1"}, {"token": "t"}, _ctx()
    )
    assert q.ok and q.data["rows"][0]["id"] == "r1"
    s = await conn.execute("notion.search", {"query": "hi"}, {}, {"token": "t"}, _ctx())
    assert s.ok and s.data["results"][0]["id"] == "p1"
    a = await conn.execute(
        "notion.append_block", {"page_id": "pg", "text": "note"}, {}, {"token": "t"}, _ctx()
    )
    assert a.ok and a.data["appended"] is True


async def test_notion_missing_database_and_token(set_connector_http):
    set_connector_http(_notion_handler({}))
    conn = NotionConnector()
    no_token = await conn.execute("notion.search", {}, {}, {}, _ctx())
    assert not no_token.ok and "token" in no_token.error
    no_db = await conn.execute("notion.query_database", {}, {}, {"token": "t"}, _ctx())
    assert not no_db.ok and "database_id" in no_db.error


# --- Webhook ------------------------------------------------------------------


async def test_webhook_send_with_signature(set_connector_http):
    captured: dict = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["sig"] = request.headers.get("x-legate-signature")
        captured["body"] = request.content.decode()
        return httpx.Response(200, json={"ok": True})

    set_connector_http(handler)
    conn = WebhookConnector()
    res = await conn.execute(
        "webhook.send",
        {"payload": {"event": "deploy"}},
        {"url": "https://hooks.example.com/x"},
        {"signing_secret": "shh"},
        _ctx(),
    )
    assert res.ok and res.data["signed"] is True
    assert captured["sig"].startswith("sha256=")
    assert captured["body"] == '{"event":"deploy"}'


async def test_webhook_text_shortcut_unsigned(set_connector_http):
    captured: dict = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["sig"] = request.headers.get("x-legate-signature")
        captured["body"] = request.content.decode()
        return httpx.Response(204)

    set_connector_http(handler)
    conn = WebhookConnector()
    res = await conn.execute(
        "webhook.send", {"text": "hello"}, {"url": "https://hooks.example.com/x"}, {}, _ctx()
    )
    assert res.ok and res.data["signed"] is False
    assert captured["sig"] is None
    assert captured["body"] == '{"text":"hello"}'


def test_webhook_validates_url():
    conn = WebhookConnector()
    conn.validate_config({"url": "https://ok.example.com"})  # ok
    with pytest.raises(ConnectorConfigError):
        conn.validate_config({"url": "not-a-url"})
    with pytest.raises(ConnectorConfigError):
        conn.validate_config({})  # url required


async def test_webhook_test_connection_does_not_fire():
    conn = WebhookConnector()
    ok = await conn.test_connection({"url": "https://x"}, {})
    assert ok.ok
    bad = await conn.test_connection({}, {})
    assert not bad.ok


# --- Registry -----------------------------------------------------------------


def test_new_connectors_are_registered():
    for t in ("github", "notion", "webhook"):
        assert get_connector(t) is not None

    catalog = {s.name for s in catalog_tool_specs()}
    assert {"github.create_issue", "notion.create_page", "webhook.send"} <= catalog

    known = known_tool_names()
    assert {"github.get_issue", "notion.search", "webhook.send"} <= known


def test_sensitive_flags_are_correct():
    specs = {s.name: s for s in catalog_tool_specs()}
    # writes are sensitive (policy-gated); reads are not.
    assert specs["github.create_issue"].is_sensitive is True
    assert specs["github.get_issue"].is_sensitive is False
    assert specs["notion.create_page"].is_sensitive is True
    assert specs["notion.search"].is_sensitive is False
    assert specs["webhook.send"].is_sensitive is True
