"""Tests for the Dobby Slack NL parser."""
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

from dobby.slack.parser import SlackParser

# ---------------------------------------------------------------------------
# Claude-backed parser tests (API mocked)
# ---------------------------------------------------------------------------

async def test_parse_simple_task():
    parser = SlackParser()
    mock_response = MagicMock()
    mock_response.content = [
        MagicMock(
            text=(
                '{"project": "spare", "description": "add warmup scheduler", '
                '"exit_criteria": "PR ready", "priority": "normal", '
                '"schedule_type": null, "schedule_expr": null, "mode": "quick"}'
            )
        )
    ]

    with patch.object(parser, "_client") as mock_client:
        mock_client.messages.create = AsyncMock(return_value=mock_response)
        result = await parser.parse(
            "add warmup scheduler to SPARE, make a PR", ["spare", "memagent"]
        )

    assert result["project"] == "spare"
    assert result["description"] == "add warmup scheduler"
    assert result["exit_criteria"] == "PR ready"


async def test_parse_scheduled_task():
    parser = SlackParser()
    mock_response = MagicMock()
    mock_response.content = [
        MagicMock(
            text=(
                '{"project": "spare", "description": "run eval suite", '
                '"exit_criteria": "report metrics", "priority": "normal", '
                '"schedule_type": "cron", "schedule_expr": "0 3 * * *", "mode": "quick"}'
            )
        )
    ]

    with patch.object(parser, "_client") as mock_client:
        mock_client.messages.create = AsyncMock(return_value=mock_response)
        result = await parser.parse(
            "run SPARE eval suite every night at 3am", ["spare", "memagent"]
        )

    assert result["schedule_type"] == "cron"
    assert result["schedule_expr"] == "0 3 * * *"


async def test_parse_with_unclear_project():
    parser = SlackParser()
    mock_response = MagicMock()
    mock_response.content = [
        MagicMock(
            text=(
                '{"project": null, "description": "fix the bug", '
                '"exit_criteria": null, "priority": "normal", '
                '"schedule_type": null, "schedule_expr": null, "mode": "quick"}'
            )
        )
    ]

    with patch.object(parser, "_client") as mock_client:
        mock_client.messages.create = AsyncMock(return_value=mock_response)
        result = await parser.parse("fix the bug", ["spare", "memagent"])

    assert result["project"] is None


# ---------------------------------------------------------------------------
# Fallback parser (no API key)
# ---------------------------------------------------------------------------

def test_fallback_parser_no_api_key():
    """Without API key, parser should still extract basic info."""
    parser = SlackParser(anthropic_api_key=None)
    # Should not crash, should return a basic dict
    result = asyncio.run(parser.parse("add tests to spare", ["spare", "memagent"]))
    assert "description" in result


async def test_fallback_detects_project():
    """Fallback parser should recognise project names from the available list."""
    parser = SlackParser(anthropic_api_key=None)
    result = await parser.parse("run the eval for memagent", ["spare", "memagent"])
    assert result["project"] == "memagent"


async def test_fallback_detects_cron_schedule():
    """Fallback parser should produce a cron schedule for nightly tasks."""
    parser = SlackParser(anthropic_api_key=None)
    result = await parser.parse(
        "run spare evals every night at 3am", ["spare", "memagent"]
    )
    assert result["schedule_type"] == "cron"
    assert result["schedule_expr"] is not None


async def test_fallback_detects_interval_schedule():
    """Fallback parser should detect interval schedules like 'every 30 minutes'."""
    parser = SlackParser(anthropic_api_key=None)
    result = await parser.parse(
        "check spare build every 30 minutes", ["spare", "memagent"]
    )
    assert result["schedule_type"] == "interval"
    assert "30" in result["schedule_expr"]


async def test_fallback_detects_urgent_priority():
    parser = SlackParser(anthropic_api_key=None)
    result = await parser.parse("urgent: fix the CI pipeline for spare", ["spare"])
    assert result["priority"] == "urgent"


async def test_fallback_planned_mode():
    parser = SlackParser(anthropic_api_key=None)
    result = await parser.parse("plan out the new data pipeline for spare", ["spare"])
    assert result["mode"] == "planned"


async def test_fallback_returns_all_keys():
    """Fallback result must have all expected keys."""
    parser = SlackParser(anthropic_api_key=None)
    result = await parser.parse("do something", ["spare"])
    expected_keys = {
        "project", "description", "exit_criteria", "priority",
        "schedule_type", "schedule_expr", "mode",
    }
    assert expected_keys.issubset(result.keys())


# ---------------------------------------------------------------------------
# Normalise helper
# ---------------------------------------------------------------------------

def test_normalise_fills_missing_keys():
    partial = {"project": "spare", "description": "test"}
    result = SlackParser._normalise(partial)
    assert result["priority"] == "normal"
    assert result["mode"] == "quick"
    assert result["schedule_type"] is None
    assert result["exit_criteria"] is None


async def test_parse_json_embedded_in_text():
    """Parser should handle Claude responses with JSON embedded in prose."""
    parser = SlackParser()
    mock_response = MagicMock()
    # Simulate Claude wrapping JSON in extra text
    mock_response.content = [
        MagicMock(
            text=(
                'Here is the parsed task:\n'
                '{"project": "spare", "description": "add tests", '
                '"exit_criteria": null, "priority": "high", '
                '"schedule_type": null, "schedule_expr": null, "mode": "quick"}\n'
                'Hope that helps!'
            )
        )
    ]

    with patch.object(parser, "_client") as mock_client:
        mock_client.messages.create = AsyncMock(return_value=mock_response)
        result = await parser.parse("add tests to spare with high priority", ["spare"])

    assert result["project"] == "spare"
    assert result["priority"] == "high"
