"""Unit tests for CustomReportService.list_reports method.

Tests the service layer business logic for listing individual custom reports.
"""

import json
from unittest.mock import AsyncMock, MagicMock

import pytest

from testio_mcp.services.custom_report_service import CustomReportService


def _make_mock_session() -> AsyncMock:
    """Create a mock AsyncSession."""
    return AsyncMock()


def _make_service(session: AsyncMock | None = None) -> CustomReportService:
    """Create a CustomReportService with mock dependencies."""
    return CustomReportService(
        session=session or _make_mock_session(),
        customer_id=1,
    )


@pytest.mark.unit
@pytest.mark.asyncio
async def test_list_reports_empty_result() -> None:
    """Return empty reports when no custom reports exist for test IDs."""
    mock_session = _make_mock_session()

    # First exec: count query returns 0
    mock_count_result = MagicMock()
    mock_count_result.one.return_value = 0
    mock_session.exec.return_value = mock_count_result

    service = _make_service(mock_session)
    result = await service.list_reports(test_ids=[999])

    assert result["total_reports"] == 0
    assert result["reports"] == []
    assert result["pagination"]["total_count"] == 0
    assert result["pagination"]["has_more"] is False


@pytest.mark.unit
@pytest.mark.asyncio
async def test_list_reports_returns_reports() -> None:
    """Return report rows with bug context."""
    mock_session = _make_mock_session()

    # First exec: count query returns 2
    mock_count_result = MagicMock()
    mock_count_result.one.return_value = 2

    # Second exec: data query returns rows
    mock_report_1 = MagicMock()
    mock_report_1.report_data = json.dumps({"accuracy": "5", "guardrails": "pass"})
    mock_report_2 = MagicMock()
    mock_report_2.report_data = json.dumps({"accuracy": "3", "guardrails": "fail"})

    mock_data_result = MagicMock()
    mock_data_result.all.return_value = [
        (mock_report_1, 100, "Bug Title 1"),
        (mock_report_2, 101, "Bug Title 2"),
    ]

    mock_session.exec.side_effect = [mock_count_result, mock_data_result]

    service = _make_service(mock_session)
    result = await service.list_reports(test_ids=[147162])

    assert result["total_reports"] == 2
    assert len(result["reports"]) == 2
    assert result["reports"][0]["bug_id"] == 100
    assert result["reports"][0]["title"] == "Bug Title 1"
    assert result["reports"][0]["report_fields"]["accuracy"] == "5"
    assert result["reports"][1]["bug_id"] == 101


@pytest.mark.unit
@pytest.mark.asyncio
async def test_list_reports_field_filtering() -> None:
    """Only include specified fields in report_fields."""
    mock_session = _make_mock_session()

    # Count query
    mock_count_result = MagicMock()
    mock_count_result.one.return_value = 1

    # Data query
    mock_report = MagicMock()
    mock_report.report_data = json.dumps({"accuracy": "5", "guardrails": "pass", "tone": "neutral"})

    mock_data_result = MagicMock()
    mock_data_result.all.return_value = [
        (mock_report, 100, "Bug 1"),
    ]

    mock_session.exec.side_effect = [mock_count_result, mock_data_result]

    service = _make_service(mock_session)
    result = await service.list_reports(
        test_ids=[147162],
        fields=["accuracy"],
    )

    assert len(result["reports"]) == 1
    assert result["reports"][0]["report_fields"] == {"accuracy": "5"}
    assert "guardrails" not in result["reports"][0]["report_fields"]
    assert "tone" not in result["reports"][0]["report_fields"]


@pytest.mark.unit
@pytest.mark.asyncio
async def test_list_reports_pagination_has_more() -> None:
    """has_more is True when there are more pages."""
    mock_session = _make_mock_session()

    # Count query returns total=3
    mock_count_result = MagicMock()
    mock_count_result.one.return_value = 3

    # Data query returns 2 items (per_page=2, page=1)
    mock_report_1 = MagicMock()
    mock_report_1.report_data = json.dumps({"a": "1"})
    mock_report_2 = MagicMock()
    mock_report_2.report_data = json.dumps({"a": "2"})

    mock_data_result = MagicMock()
    mock_data_result.all.return_value = [
        (mock_report_1, 100, "Bug 1"),
        (mock_report_2, 101, "Bug 2"),
    ]

    mock_session.exec.side_effect = [mock_count_result, mock_data_result]

    service = _make_service(mock_session)
    result = await service.list_reports(
        test_ids=[147162],
        page=1,
        per_page=2,
    )

    assert result["pagination"]["has_more"] is True
    assert result["pagination"]["total_count"] == 3
    assert result["pagination"]["page"] == 1
    assert result["pagination"]["per_page"] == 2


@pytest.mark.unit
@pytest.mark.asyncio
async def test_list_reports_skips_invalid_json() -> None:
    """Reports with invalid JSON are skipped gracefully."""
    mock_session = _make_mock_session()

    # Count query
    mock_count_result = MagicMock()
    mock_count_result.one.return_value = 2

    # Data query: one valid, one invalid JSON
    mock_report_valid = MagicMock()
    mock_report_valid.report_data = json.dumps({"accuracy": "5"})
    mock_report_invalid = MagicMock()
    mock_report_invalid.report_data = "not valid json"

    mock_data_result = MagicMock()
    mock_data_result.all.return_value = [
        (mock_report_valid, 100, "Bug 1"),
        (mock_report_invalid, 101, "Bug 2"),
    ]

    mock_session.exec.side_effect = [mock_count_result, mock_data_result]

    service = _make_service(mock_session)
    result = await service.list_reports(test_ids=[147162])

    # Only the valid report should be in results
    assert len(result["reports"]) == 1
    assert result["reports"][0]["bug_id"] == 100
