"""Tests for PyAgent.generate_coverage."""

import os
from pathlib import Path
from typing import Any
from unittest.mock import patch

import pytest

from py_agent.api.py_agent import PyAgent

RUN_COVERAGE_TARGET = "py_agent.api.py_agent.run_coverage"


@pytest.fixture
def agent() -> PyAgent:
    return PyAgent()


# ---------------------------------------------------------------------------
# Helper: build the default expected report path given a project root
# ---------------------------------------------------------------------------


def _default_report_path(project_root: str) -> str:
    return os.path.join(project_root, ".coverage_report", "coverage.json")


# ---------------------------------------------------------------------------
# Tests: project_root resolution
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_no_args_uses_cwd_as_project_root(agent: PyAgent) -> None:
    """When project_path is None, os.getcwd() is used as the project root."""
    mock_result: dict[str, Any] = {"success": True}
    cwd = "/mocked/cwd"

    with (
        patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run,
        patch("os.getcwd", return_value=cwd),
    ):
        await agent.generate_coverage()

    mock_run.assert_called_once()
    call_kwargs = mock_run.call_args
    assert call_kwargs.kwargs["project_path"] == cwd


@pytest.mark.asyncio
async def test_string_project_path_passed_directly(agent: PyAgent) -> None:
    """When project_path is a string, it is used as the project root unchanged."""
    mock_result: dict[str, Any] = {"success": True}
    project = "/my/project"

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(project_path=project)

    mock_run.assert_called_once()
    assert mock_run.call_args.kwargs["project_path"] == project


@pytest.mark.asyncio
async def test_path_object_project_path_converted_to_str(agent: PyAgent) -> None:
    """When project_path is a Path object, it is converted to str."""
    mock_result: dict[str, Any] = {"success": True}
    project = Path("/my/project")

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(project_path=project)

    mock_run.assert_called_once()
    assert mock_run.call_args.kwargs["project_path"] == str(project)


# ---------------------------------------------------------------------------
# Tests: coverage_report_path resolution
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_no_args_builds_default_report_path(agent: PyAgent) -> None:
    """When coverage_report_path is None, the default path under project root is used."""
    mock_result: dict[str, Any] = {"success": True}
    cwd = "/mocked/cwd"

    with (
        patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run,
        patch("os.getcwd", return_value=cwd),
    ):
        await agent.generate_coverage()

    expected_report = _default_report_path(cwd)
    assert mock_run.call_args.kwargs["coverage_report_path"] == expected_report


@pytest.mark.asyncio
async def test_default_report_path_uses_project_root_prefix(agent: PyAgent) -> None:
    """Default report path is <project_path>/.coverage_report/coverage.json."""
    mock_result: dict[str, Any] = {"success": True}
    project = "/custom/root"

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(project_path=project)

    expected_report = _default_report_path(project)
    assert mock_run.call_args.kwargs["coverage_report_path"] == expected_report


@pytest.mark.asyncio
async def test_string_coverage_report_path_passed_directly(agent: PyAgent) -> None:
    """When coverage_report_path is a string, it is used unchanged."""
    mock_result: dict[str, Any] = {"success": True}
    report = "/reports/my_coverage.json"

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(
            project_path="/some/project",
            coverage_report_path=report,
        )

    assert mock_run.call_args.kwargs["coverage_report_path"] == report


@pytest.mark.asyncio
async def test_path_object_coverage_report_path_converted_to_str(agent: PyAgent) -> None:
    """When coverage_report_path is a Path object, it is converted to str."""
    mock_result: dict[str, Any] = {"success": True}
    report = Path("/reports/my_coverage.json")

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(
            project_path="/some/project",
            coverage_report_path=report,
        )

    assert mock_run.call_args.kwargs["coverage_report_path"] == str(report)


# ---------------------------------------------------------------------------
# Tests: both arguments provided
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_both_args_as_strings(agent: PyAgent) -> None:
    """Both project_path and coverage_report_path provided as strings are forwarded."""
    mock_result: dict[str, Any] = {"success": True}
    project = "/proj"
    report = "/proj/reports/cov.json"

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(
            project_path=project,
            coverage_report_path=report,
        )

    mock_run.assert_called_once_with(project_path=project, coverage_report_path=report)


@pytest.mark.asyncio
async def test_both_args_as_path_objects(agent: PyAgent) -> None:
    """Both project_path and coverage_report_path as Path objects are converted to str."""
    mock_result: dict[str, Any] = {"success": True}
    project = Path("/proj")
    report = Path("/proj/reports/cov.json")

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result) as mock_run:
        await agent.generate_coverage(
            project_path=project,
            coverage_report_path=report,
        )

    mock_run.assert_called_once_with(
        project_path=str(project),
        coverage_report_path=str(report),
    )


# ---------------------------------------------------------------------------
# Tests: return value propagation
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_success_result_propagated(agent: PyAgent) -> None:
    """A success result from run_coverage is returned as-is."""
    mock_result: dict[str, Any] = {"success": True, "coverage": 85.5}

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result):
        result = await agent.generate_coverage(project_path="/proj")

    assert result == mock_result


@pytest.mark.asyncio
async def test_error_result_propagated(agent: PyAgent) -> None:
    """An error result from run_coverage is returned as-is."""
    mock_result: dict[str, Any] = {"success": False, "error": "No test files found"}

    with patch(RUN_COVERAGE_TARGET, return_value=mock_result):
        result = await agent.generate_coverage(project_path="/proj")

    assert result == mock_result
