"""Tests for PlanningEngine — uses mock LLM to verify plan generation."""

import json
from unittest.mock import AsyncMock

import pytest

from fliiq.runtime.llm.providers import BaseLLM, LLMConfig, LLMProvider, LLMResponse
from fliiq.runtime.planning.engine import PlanningEngine, _extract_json
from fliiq.runtime.planning.models import Plan


_MOCK_PLAN_JSON = json.dumps({
    "goal": "Research the latest AI agent frameworks",
    "steps": [
        {"title": "Survey landscape", "description": "Identify top 5 frameworks", "skill": "web_fetch", "dependencies": []},
        {"title": "Compare features", "description": "Create comparison matrix", "skill": None, "dependencies": [0]},
        {"title": "Write summary", "description": "Summarize findings", "skill": None, "dependencies": [1]},
    ],
})


def _make_mock_llm(response_content: str = _MOCK_PLAN_JSON) -> BaseLLM:
    mock = AsyncMock(spec=BaseLLM)
    mock.generate = AsyncMock(return_value=LLMResponse(
        content=response_content,
        model="test-model",
        provider=LLMProvider.ANTHROPIC,
    ))
    mock.config = LLMConfig(provider=LLMProvider.ANTHROPIC, api_key="test")
    return mock


async def test_generate_plan_basic(tmp_path):
    # Create SOUL.md in tmp project root
    (tmp_path / "SOUL.md").write_text("You are Fliiq.")

    engine = PlanningEngine(llm=_make_mock_llm(), project_root=tmp_path)
    plan = await engine.generate_plan("research the latest AI agent frameworks")

    assert isinstance(plan, Plan)
    assert plan.goal == "Research the latest AI agent frameworks"
    assert len(plan.steps) == 3
    assert plan.steps[0].title == "Survey landscape"
    assert plan.steps[0].skill == "web_fetch"
    assert plan.steps[1].dependencies == ["0"]
    assert plan.prompt == "research the latest AI agent frameworks"


async def test_generate_plan_with_fenced_json(tmp_path):
    (tmp_path / "SOUL.md").write_text("You are Fliiq.")
    fenced = f"Here's the plan:\n```json\n{_MOCK_PLAN_JSON}\n```\nDone."

    engine = PlanningEngine(llm=_make_mock_llm(fenced), project_root=tmp_path)
    plan = await engine.generate_plan("test prompt")

    assert len(plan.steps) == 3


async def test_generate_plan_includes_metadata(tmp_path):
    (tmp_path / "SOUL.md").write_text("You are Fliiq.")

    engine = PlanningEngine(llm=_make_mock_llm(), project_root=tmp_path)
    plan = await engine.generate_plan("test")

    assert plan.metadata["provider"] == "anthropic"
    assert plan.metadata["model"] == "test-model"


async def test_generate_plan_loads_playbook_for_coding(tmp_path):
    (tmp_path / "SOUL.md").write_text("soul")
    pb_dir = tmp_path / "playbooks"
    pb_dir.mkdir()
    (pb_dir / "coding.md").write_text("# Coding standards")

    mock_llm = _make_mock_llm()
    engine = PlanningEngine(llm=mock_llm, project_root=tmp_path)
    await engine.generate_plan("build a Flask API with database endpoints")

    # Verify system prompt was passed to LLM containing playbook
    call_args = mock_llm.generate.call_args
    system = call_args.kwargs.get("system")
    assert system is not None, f"Expected system kwarg, got: {call_args}"
    assert "Coding standards" in system


def test_extract_json_direct():
    data = _extract_json('{"goal": "test", "steps": []}')
    assert data["goal"] == "test"


def test_extract_json_fenced():
    text = "Some text\n```json\n{\"goal\": \"test\"}\n```\nMore text"
    data = _extract_json(text)
    assert data["goal"] == "test"


def test_extract_json_invalid_raises():
    with pytest.raises(ValueError, match="Could not extract valid JSON"):
        _extract_json("not json at all")
