from __future__ import annotations

import asyncio
from typing import Any

import pytest

from agent.dashboard import thread_api


def test_resolve_repo_config_parses_request_repo() -> None:
    assert thread_api._resolve_repo_config("octo/repo") == {"owner": "octo", "name": "repo"}


def test_resolve_repo_config_returns_empty_when_no_repo_given() -> None:
    assert thread_api._resolve_repo_config(None) == {}
    assert thread_api._resolve_repo_config("") == {}
    assert thread_api._resolve_repo_config("not-a-repo") == {}


def test_thread_summary_blanks_repo_when_absent() -> None:
    summary = thread_api._thread_summary(
        {"thread_id": "t1", "metadata": {"source": "dashboard", "title": "no repo run"}}
    )
    assert summary["repo"] == ""
    assert summary["repoFullName"] == ""


def test_thread_summary_keeps_repo_when_present() -> None:
    summary = thread_api._thread_summary(
        {
            "thread_id": "t2",
            "metadata": {
                "source": "dashboard",
                "title": "repo run",
                "repo_owner": "octo",
                "repo_name": "repo",
            },
        }
    )
    assert summary["repo"] == "repo"
    assert summary["repoFullName"] == "octo/repo"


def test_thread_summary_includes_trace_url(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(
        thread_api,
        "get_langsmith_trace_url",
        lambda thread_id: f"https://smith.example/t/{thread_id}",
    )
    summary = thread_api._thread_summary(
        {"thread_id": "t3", "metadata": {"source": "dashboard", "title": "traced run"}}
    )
    assert summary["traceUrl"] == "https://smith.example/t/t3"


class _FakeThreadsClient:
    async def create(
        self, *, thread_id: str, metadata: dict[str, Any], if_exists: str
    ) -> dict[str, Any]:
        return {"thread_id": thread_id, "metadata": metadata, "if_exists": if_exists}

    async def update(self, *, thread_id: str, metadata: dict[str, Any]) -> dict[str, Any]:
        return {"thread_id": thread_id, "metadata": metadata}

    async def get(self, thread_id: str) -> dict[str, Any]:
        return {"thread_id": thread_id, "metadata": {}}


class _FakeRunsClient:
    def __init__(self) -> None:
        self.configurable: dict[str, Any] | None = None

    async def create(
        self,
        thread_id: str,
        assistant_id: str,
        *,
        input: dict[str, Any],
        config: dict[str, Any],
        if_not_exists: str = "reject",
        stream_mode: list[str] | None = None,
        stream_resumable: bool = False,
    ) -> dict[str, str]:
        self.configurable = config["configurable"]
        return {"run_id": "run-id"}


class _FakeLangGraphClient:
    def __init__(self) -> None:
        self.threads = _FakeThreadsClient()
        self.runs = _FakeRunsClient()


@pytest.fixture
def dashboard_run_client(monkeypatch: pytest.MonkeyPatch) -> _FakeLangGraphClient:
    client = _FakeLangGraphClient()

    async def fake_get_profile(login: str) -> dict[str, Any]:
        return {}

    async def fake_ensure_token(login: str) -> None:
        return None

    async def fake_resolve_email(login: str, profile: dict[str, Any]) -> str:
        return "octo@example.com"

    monkeypatch.setattr(thread_api, "langgraph_client", lambda: client)
    monkeypatch.setattr(thread_api, "get_profile", fake_get_profile)
    monkeypatch.setattr(thread_api, "_ensure_dashboard_github_token", fake_ensure_token)
    monkeypatch.setattr(thread_api, "_resolve_run_email", fake_resolve_email)
    return client


def test_create_thread_record_omits_repo_less_marker_when_repo_unset(
    dashboard_run_client: _FakeLangGraphClient,
) -> None:
    # Runs now start client-side via the stream commands endpoint, so the run
    # configurable is assembled from thread metadata by
    # ``_build_dashboard_configurable``. The thread record must not persist a
    # repo-less marker when the repo is simply unset (not explicitly cleared).
    asyncio.run(
        thread_api._create_dashboard_thread_record(
            "thread-id",
            login="octo",
            repo_config={},
            prompt="do work",
        )
    )

    configurable = asyncio.run(
        thread_api._build_dashboard_configurable("thread-id", "octo", {"source": "dashboard"})
    )
    assert "repo_explicitly_none" not in configurable
    assert "repo" not in configurable


def test_build_configurable_marks_repo_less_config_when_explicit(
    dashboard_run_client: _FakeLangGraphClient,
) -> None:
    configurable = asyncio.run(
        thread_api._build_dashboard_configurable(
            "thread-id",
            "octo",
            {"source": "dashboard", "repo_explicitly_none": True},
        )
    )
    assert configurable["repo_explicitly_none"] is True
    assert "repo" not in configurable


def test_build_configurable_includes_repo_when_configured(
    dashboard_run_client: _FakeLangGraphClient,
) -> None:
    configurable = asyncio.run(
        thread_api._build_dashboard_configurable(
            "thread-id",
            "octo",
            {"source": "dashboard", "repo_owner": "octo", "repo_name": "repo"},
        )
    )
    assert configurable["repo"] == {"owner": "octo", "name": "repo"}
    assert "repo_explicitly_none" not in configurable
