"""Tests for PyAgent.get_coverage_tree."""

import json
from pathlib import Path
from unittest.mock import patch

import pytest

from py_agent.api.py_agent import PyAgent


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


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------


def _write_json(path: Path, data: object) -> Path:
    """Write *data* as JSON to *path* and return the path."""
    path.write_text(json.dumps(data), encoding="utf-8")
    return path


# ---------------------------------------------------------------------------
# Error path: file not found
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_returns_error_when_file_does_not_exist(agent: PyAgent) -> None:
    """Non-existent path returns error dict with the path in the message."""
    result = await agent.get_coverage_tree("/nonexistent/coverage.json")
    assert result == {"error": "Coverage report not found at /nonexistent/coverage.json"}


@pytest.mark.asyncio
async def test_returns_error_when_path_object_does_not_exist(
    agent: PyAgent, tmp_path: Path
) -> None:
    """A Path object that does not exist also returns an error dict."""
    missing = tmp_path / "missing.json"
    result = await agent.get_coverage_tree(missing)
    assert "error" in result
    assert str(missing) in result["error"]


@pytest.mark.asyncio
async def test_error_message_contains_exact_report_path(agent: PyAgent) -> None:
    """The error value embeds the exact string representation of the path."""
    path_str = "/some/path/to/coverage.json"
    result = await agent.get_coverage_tree(path_str)
    assert result["error"] == f"Coverage report not found at {path_str}"


# ---------------------------------------------------------------------------
# Happy path: file exists
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_returns_parsed_json_for_existing_file(agent: PyAgent, tmp_path: Path) -> None:
    """Valid JSON file is loaded and returned as a dict."""
    data = {"totals": {"covered_lines": 42, "percent_covered": 95.5}}
    report = _write_json(tmp_path / "coverage.json", data)
    result = await agent.get_coverage_tree(str(report))
    assert result == data


@pytest.mark.asyncio
async def test_accepts_path_object_for_existing_file(agent: PyAgent, tmp_path: Path) -> None:
    """coverage_report_path may be a Path object instead of a string."""
    data = {"files": {"src/foo.py": {"executed_lines": [1, 2, 3]}}}
    report = _write_json(tmp_path / "cov.json", data)
    result = await agent.get_coverage_tree(report)  # Pass Path directly
    assert result == data


@pytest.mark.asyncio
async def test_returns_empty_dict_for_empty_json_object(agent: PyAgent, tmp_path: Path) -> None:
    """An empty JSON object {} is returned unchanged."""
    report = _write_json(tmp_path / "empty.json", {})
    result = await agent.get_coverage_tree(report)
    assert result == {}


@pytest.mark.asyncio
async def test_returns_deeply_nested_structure(agent: PyAgent, tmp_path: Path) -> None:
    """Deeply nested JSON is returned without modification."""
    data: dict = {
        "meta": {"version": "7.0"},
        "files": {
            "src/module.py": {
                "executed_lines": [1, 2, 3],
                "missing_lines": [],
                "excluded_lines": [],
                "summary": {"covered_lines": 3, "percent_covered": 100.0},
            }
        },
    }
    report = _write_json(tmp_path / "deep.json", data)
    result = await agent.get_coverage_tree(report)
    assert result == data


# ---------------------------------------------------------------------------
# project_path parameter (currently unused)
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_project_path_none_does_not_affect_result(agent: PyAgent, tmp_path: Path) -> None:
    """Explicitly passing project_path=None yields the same result as omitting it."""
    data = {"totals": {"percent_covered": 80.0}}
    report = _write_json(tmp_path / "cov.json", data)
    result = await agent.get_coverage_tree(report, project_path=None)
    assert result == data


@pytest.mark.asyncio
async def test_project_path_provided_does_not_affect_result(agent: PyAgent, tmp_path: Path) -> None:
    """Passing a project_path value does not change the returned coverage data."""
    data = {"totals": {"percent_covered": 70.0}}
    report = _write_json(tmp_path / "cov.json", data)
    result = await agent.get_coverage_tree(report, project_path=tmp_path)
    assert result == data


@pytest.mark.asyncio
async def test_project_path_string_does_not_affect_result(agent: PyAgent, tmp_path: Path) -> None:
    """project_path as a string also leaves the result unaffected."""
    data = {"totals": {"percent_covered": 60.0}}
    report = _write_json(tmp_path / "cov.json", data)
    result = await agent.get_coverage_tree(report, project_path="/some/project")
    assert result == data


# ---------------------------------------------------------------------------
# os.path.exists integration: ensure mocking works as expected
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_uses_string_representation_of_path_for_existence_check(
    agent: PyAgent, tmp_path: Path
) -> None:
    """os.path.exists is called with the string form of coverage_report_path."""
    data = {"x": 1}
    report = _write_json(tmp_path / "cov.json", data)
    with patch("os.path.exists", wraps=lambda p: p == str(report)) as mock_exists:
        result = await agent.get_coverage_tree(report)
    mock_exists.assert_called_once_with(str(report))
    assert result == data
