"""Unit tests for timmy.kimi_delegation — Kimi research delegation via Gitea labels."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from timmy.kimi_delegation import (
    KIMI_LABEL_COLOR,
    KIMI_READY_LABEL,
    _build_research_template,
    _extract_action_items,
    _slugify,
    delegate_research_to_kimi,
    exceeds_local_capacity,
)

# ── Constants ─────────────────────────────────────────────────────────────────


def test_kimi_ready_label():
    assert KIMI_READY_LABEL == "kimi-ready"


def test_kimi_label_color_is_hex():
    assert KIMI_LABEL_COLOR.startswith("#")
    assert len(KIMI_LABEL_COLOR) == 7


# ── exceeds_local_capacity ────────────────────────────────────────────────────


class TestExceedsLocalCapacity:
    def test_keyword_comprehensive(self):
        assert exceeds_local_capacity("Do a comprehensive review of X") is True

    def test_keyword_deep_research(self):
        assert exceeds_local_capacity("deep research into neural networks") is True

    def test_keyword_benchmark(self):
        assert exceeds_local_capacity("benchmark these five models") is True

    def test_keyword_exhaustive(self):
        assert exceeds_local_capacity("exhaustive list of options") is True

    def test_keyword_case_insensitive(self):
        assert exceeds_local_capacity("COMPREHENSIVE analysis") is True

    def test_keyword_survey(self):
        assert exceeds_local_capacity("survey all available tools") is True

    def test_keyword_extensive(self):
        assert exceeds_local_capacity("extensive documentation needed") is True

    def test_short_simple_task(self):
        assert exceeds_local_capacity("fix the login bug") is False

    def test_long_task_exceeds_word_threshold(self):
        long_task = " ".join(["word"] * 55)
        assert exceeds_local_capacity(long_task) is True

    def test_exactly_at_threshold(self):
        at_threshold = " ".join(["word"] * 50)
        assert exceeds_local_capacity(at_threshold) is True

    def test_just_below_threshold(self):
        short = " ".join(["word"] * 49)
        assert exceeds_local_capacity(short) is False

    def test_empty_string(self):
        assert exceeds_local_capacity("") is False


# ── _slugify ──────────────────────────────────────────────────────────────────


class TestSlugify:
    def test_simple_text(self):
        assert _slugify("Hello World") == "hello-world"

    def test_special_characters_removed(self):
        assert _slugify("Hello, World!") == "hello-world"

    def test_underscores_become_dashes(self):
        assert _slugify("hello_world") == "hello-world"

    def test_multiple_spaces(self):
        assert _slugify("hello   world") == "hello-world"

    def test_truncates_to_60(self):
        long = "a" * 80
        result = _slugify(long)
        assert len(result) <= 60

    def test_no_leading_trailing_dashes(self):
        result = _slugify("  hello  ")
        assert not result.startswith("-")
        assert not result.endswith("-")

    def test_empty_string(self):
        assert _slugify("") == ""


# ── _build_research_template ──────────────────────────────────────────────────


class TestBuildResearchTemplate:
    def test_contains_task(self):
        body = _build_research_template("My Task", "some context", "What is X?")
        assert "My Task" in body

    def test_contains_question(self):
        body = _build_research_template("Task", "ctx", "What is the answer?")
        assert "What is the answer?" in body

    def test_contains_context(self):
        body = _build_research_template("Task", "project background", "Q?")
        assert "project background" in body

    def test_contains_kimi_ready_label(self):
        body = _build_research_template("Task", "ctx", "Q?")
        assert KIMI_READY_LABEL in body

    def test_default_priority_normal(self):
        body = _build_research_template("Task", "ctx", "Q?")
        assert "normal" in body

    def test_custom_priority_high(self):
        body = _build_research_template("Task", "ctx", "Q?", priority="high")
        assert "high" in body

    def test_contains_deliverables_section(self):
        body = _build_research_template("Task", "ctx", "Q?")
        assert "Deliverables" in body

    def test_slug_in_artifact_path(self):
        body = _build_research_template("My Research Task", "ctx", "Q?")
        assert "my-research-task" in body

    def test_contains_research_request_header(self):
        body = _build_research_template("Task", "ctx", "Q?")
        assert "## Research Request" in body


# ── _extract_action_items ─────────────────────────────────────────────────────


class TestExtractActionItems:
    def test_checkbox_items(self):
        text = "- [ ] Do thing A\n- [ ] Do thing B"
        items = _extract_action_items(text)
        assert "Do thing A" in items
        assert "Do thing B" in items

    def test_numbered_list(self):
        text = "1. First step\n2. Second step\n3. Third step"
        items = _extract_action_items(text)
        assert "First step" in items
        assert "Second step" in items
        assert "Third step" in items

    def test_action_prefix(self):
        text = "Action: Implement caching layer"
        items = _extract_action_items(text)
        assert "Implement caching layer" in items

    def test_todo_prefix(self):
        text = "TODO: Write tests"
        items = _extract_action_items(text)
        assert "Write tests" in items

    def test_next_step_prefix(self):
        text = "Next step: Deploy to staging"
        items = _extract_action_items(text)
        assert "Deploy to staging" in items

    def test_case_insensitive_prefixes(self):
        text = "TODO: Upper\ntodo: lower\nTodo: Mixed"
        items = _extract_action_items(text)
        assert len(items) == 3

    def test_deduplication(self):
        text = "1. Do the thing\n2. Do the thing"
        items = _extract_action_items(text)
        assert items.count("Do the thing") == 1

    def test_empty_text(self):
        assert _extract_action_items("") == []

    def test_no_action_items(self):
        text = "This is just a paragraph with no action items."
        assert _extract_action_items(text) == []

    def test_returns_list(self):
        assert isinstance(_extract_action_items("1. Item"), list)


# ── delegate_research_to_kimi ─────────────────────────────────────────────────


class TestDelegateResearchToKimi:
    @pytest.mark.asyncio
    async def test_empty_task_returns_error(self):
        result = await delegate_research_to_kimi("", "context", "question?")
        assert result["success"] is False
        assert "task" in result["error"].lower()

    @pytest.mark.asyncio
    async def test_whitespace_task_returns_error(self):
        result = await delegate_research_to_kimi("   ", "context", "question?")
        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_empty_question_returns_error(self):
        result = await delegate_research_to_kimi("Task title", "context", "")
        assert result["success"] is False
        assert "question" in result["error"].lower()

    @pytest.mark.asyncio
    async def test_whitespace_question_returns_error(self):
        result = await delegate_research_to_kimi("Task", "ctx", "   ")
        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_delegates_to_create_issue(self):
        with patch(
            "timmy.kimi_delegation.create_kimi_research_issue",
            new_callable=AsyncMock,
            return_value={
                "success": True,
                "issue_number": 42,
                "issue_url": "http://x/42",
                "error": None,
            },
        ) as mock_create:
            result = await delegate_research_to_kimi("Task", "ctx", "What is X?", "high")
            mock_create.assert_awaited_once_with("Task", "ctx", "What is X?", "high")
            assert result["success"] is True
            assert result["issue_number"] == 42

    @pytest.mark.asyncio
    async def test_passes_default_priority(self):
        with patch(
            "timmy.kimi_delegation.create_kimi_research_issue",
            new_callable=AsyncMock,
            return_value={"success": True, "issue_number": 1, "issue_url": "", "error": None},
        ) as mock_create:
            await delegate_research_to_kimi("Task", "ctx", "Q?")
            _, _, _, priority = mock_create.call_args.args
            assert priority == "normal"


# ── create_kimi_research_issue ────────────────────────────────────────────────


class TestCreateKimiResearchIssue:
    @pytest.mark.asyncio
    async def test_no_gitea_token_returns_error(self):
        from timmy.kimi_delegation import create_kimi_research_issue

        mock_settings = MagicMock()
        mock_settings.gitea_enabled = True
        mock_settings.gitea_token = ""

        with patch("config.settings", mock_settings):
            result = await create_kimi_research_issue("Task", "ctx", "Q?")
        assert result["success"] is False
        assert "not configured" in result["error"]

    @pytest.mark.asyncio
    async def test_gitea_disabled_returns_error(self):
        from timmy.kimi_delegation import create_kimi_research_issue

        mock_settings = MagicMock()
        mock_settings.gitea_enabled = False
        mock_settings.gitea_token = "tok"

        with patch("config.settings", mock_settings):
            result = await create_kimi_research_issue("Task", "ctx", "Q?")
        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_successful_issue_creation(self):
        from timmy.kimi_delegation import create_kimi_research_issue

        mock_settings = MagicMock()
        mock_settings.gitea_enabled = True
        mock_settings.gitea_token = "fake-token"
        mock_settings.gitea_url = "http://gitea.local"
        mock_settings.gitea_repo = "owner/repo"

        label_resp = MagicMock()
        label_resp.status_code = 200
        label_resp.json.return_value = [{"name": "kimi-ready", "id": 7}]

        issue_resp = MagicMock()
        issue_resp.status_code = 201
        issue_resp.json.return_value = {
            "number": 101,
            "html_url": "http://gitea.local/issues/101",
        }

        mock_client = AsyncMock()
        mock_client.get.return_value = label_resp
        mock_client.post.return_value = issue_resp

        async_ctx = AsyncMock()
        async_ctx.__aenter__.return_value = mock_client
        async_ctx.__aexit__.return_value = False

        with (
            patch("config.settings", mock_settings),
            patch("httpx.AsyncClient", return_value=async_ctx),
        ):
            result = await create_kimi_research_issue("Task", "ctx", "Q?")

        assert result["success"] is True
        assert result["issue_number"] == 101
        assert result["error"] is None

    @pytest.mark.asyncio
    async def test_api_error_returns_failure(self):
        from timmy.kimi_delegation import create_kimi_research_issue

        mock_settings = MagicMock()
        mock_settings.gitea_enabled = True
        mock_settings.gitea_token = "tok"
        mock_settings.gitea_url = "http://gitea.local"
        mock_settings.gitea_repo = "owner/repo"

        label_resp = MagicMock()
        label_resp.status_code = 200
        label_resp.json.return_value = [{"name": "kimi-ready", "id": 7}]

        issue_resp = MagicMock()
        issue_resp.status_code = 500
        issue_resp.text = "Internal Server Error"

        mock_client = AsyncMock()
        mock_client.get.return_value = label_resp
        mock_client.post.return_value = issue_resp

        async_ctx = AsyncMock()
        async_ctx.__aenter__.return_value = mock_client
        async_ctx.__aexit__.return_value = False

        with (
            patch("config.settings", mock_settings),
            patch("httpx.AsyncClient", return_value=async_ctx),
        ):
            result = await create_kimi_research_issue("Task", "ctx", "Q?")

        assert result["success"] is False
        assert "500" in result["error"]


# ── index_kimi_artifact ───────────────────────────────────────────────────────


class TestIndexKimiArtifact:
    @pytest.mark.asyncio
    async def test_empty_artifact_returns_error(self):
        from timmy.kimi_delegation import index_kimi_artifact

        result = await index_kimi_artifact(42, "Title", "")
        assert result["success"] is False
        assert "Empty" in result["error"]

    @pytest.mark.asyncio
    async def test_whitespace_only_artifact_returns_error(self):
        from timmy.kimi_delegation import index_kimi_artifact

        result = await index_kimi_artifact(42, "Title", "   \n  ")
        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_successful_indexing(self):
        from timmy.kimi_delegation import index_kimi_artifact

        mock_entry = MagicMock()
        mock_entry.id = "mem-abc-123"

        with patch("timmy.memory_system.store_memory", return_value=mock_entry) as mock_store:
            result = await index_kimi_artifact(55, "Research Title", "Artifact content here.")

        assert result["success"] is True
        assert result["memory_id"] == "mem-abc-123"
        mock_store.assert_called_once()
        call_kwargs = mock_store.call_args.kwargs
        assert call_kwargs["source"] == "kimi"
        assert call_kwargs["context_type"] == "document"
        assert call_kwargs["task_id"] == "55"

    @pytest.mark.asyncio
    async def test_store_memory_exception_returns_error(self):
        from timmy.kimi_delegation import index_kimi_artifact

        with patch(
            "timmy.memory_system.store_memory",
            side_effect=RuntimeError("DB error"),
        ):
            result = await index_kimi_artifact(1, "T", "Some content")
        assert result["success"] is False
        assert "DB error" in result["error"]


# ── extract_and_create_followups ──────────────────────────────────────────────


class TestExtractAndCreateFollowups:
    @pytest.mark.asyncio
    async def test_no_action_items_returns_empty_list(self):
        from timmy.kimi_delegation import extract_and_create_followups

        result = await extract_and_create_followups("No action items here.", 10)
        assert result["success"] is True
        assert result["created"] == []
        assert result["error"] is None

    @pytest.mark.asyncio
    async def test_gitea_not_configured(self):
        from timmy.kimi_delegation import extract_and_create_followups

        mock_settings = MagicMock()
        mock_settings.gitea_enabled = False
        mock_settings.gitea_token = ""

        with patch("config.settings", mock_settings):
            result = await extract_and_create_followups("1. Do the thing", 10)
        assert result["success"] is False
        assert result["created"] == []

    @pytest.mark.asyncio
    async def test_creates_followup_issues(self):
        from timmy.kimi_delegation import extract_and_create_followups

        mock_settings = MagicMock()
        mock_settings.gitea_enabled = True
        mock_settings.gitea_token = "tok"
        mock_settings.gitea_url = "http://gitea.local"
        mock_settings.gitea_repo = "owner/repo"

        issue_resp = MagicMock()
        issue_resp.status_code = 201
        issue_resp.json.return_value = {"number": 200}

        mock_client = AsyncMock()
        mock_client.post.return_value = issue_resp

        async_ctx = AsyncMock()
        async_ctx.__aenter__.return_value = mock_client
        async_ctx.__aexit__.return_value = False

        with (
            patch("config.settings", mock_settings),
            patch("httpx.AsyncClient", return_value=async_ctx),
        ):
            result = await extract_and_create_followups("1. Do the thing\n2. Do another thing", 10)

        assert result["success"] is True
        assert 200 in result["created"]
