"""Unit tests for CustomReportService aggregation logic.

Tests auto-detection of field types and computation of statistics.
These test the pure Python logic without database access.
"""

import pytest

from testio_mcp.services.custom_report_service import SYSTEM_DIMENSIONS, CustomReportService


@pytest.mark.unit
class TestClassifyAndSummarize:
    """Tests for _classify_and_summarize method (field type auto-detection)."""

    def _make_service(self) -> CustomReportService:
        """Create a service instance for testing (session/customer_id unused)."""
        return CustomReportService(session=None, customer_id=1)  # type: ignore[arg-type]

    def test_numeric_detection_with_integers(self) -> None:
        """Verify integer values are detected as numeric."""
        service = self._make_service()
        result = service._classify_and_summarize(["6", "7", "8", "9", "10"])
        assert result["type"] == "numeric"
        assert result["count"] == 5
        assert result["avg"] == 8.0
        assert result["min"] == 6.0
        assert result["max"] == 10.0
        assert "distribution" in result

    def test_numeric_detection_with_floats(self) -> None:
        """Verify float values are detected as numeric."""
        service = self._make_service()
        result = service._classify_and_summarize(["3.5", "7.2", "8.8"])
        assert result["type"] == "numeric"
        assert result["avg"] == pytest.approx(6.5, abs=0.1)

    def test_numeric_with_some_non_numeric(self) -> None:
        """Verify >80% numeric still classifies as numeric."""
        service = self._make_service()
        # 9 out of 10 are numeric = 90% > 80% threshold
        values = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "N/A"]
        result = service._classify_and_summarize(values)
        assert result["type"] == "numeric"

    def test_categorical_detection(self) -> None:
        """Verify categorical values are detected with value counts."""
        service = self._make_service()
        values = ["Yes", "No", "No", "Yes", "Not relevant", "No"]
        result = service._classify_and_summarize(values)
        assert result["type"] == "categorical"
        assert result["count"] == 6
        assert result["values"]["No"] == 3
        assert result["values"]["Yes"] == 2
        assert result["values"]["Not relevant"] == 1

    def test_text_detection_for_many_unique_values(self) -> None:
        """Verify text classification when >20 unique non-numeric values."""
        service = self._make_service()
        # 25 unique strings → text (>20 unique and <80% numeric)
        values = [f"unique_text_{i}" for i in range(25)]
        result = service._classify_and_summarize(values)
        assert result["type"] == "text"
        assert result["count"] == 25

    def test_small_integer_range_exact_counts(self) -> None:
        """Verify small integer ranges use exact value counts instead of buckets."""
        service = self._make_service()
        values = ["1", "2", "2", "3", "3", "3"]
        result = service._classify_and_summarize(values)
        assert result["type"] == "numeric"
        dist = result["distribution"]
        assert dist["1"] == 1
        assert dist["2"] == 2
        assert dist["3"] == 3

    def test_categorical_sorted_by_count_descending(self) -> None:
        """Verify categorical values are sorted by count (most common first)."""
        service = self._make_service()
        values = ["rare", "common", "common", "common", "medium", "medium"]
        result = service._classify_and_summarize(values)
        assert result["type"] == "categorical"
        keys = list(result["values"].keys())
        assert keys[0] == "common"
        assert keys[1] == "medium"
        assert keys[2] == "rare"


@pytest.mark.unit
class TestComputeFieldSummary:
    """Tests for _compute_field_summary method."""

    def _make_service(self) -> CustomReportService:
        return CustomReportService(session=None, customer_id=1)  # type: ignore[arg-type]

    def test_summarizes_all_fields(self) -> None:
        """Verify all fields in reports are summarized."""
        service = self._make_service()
        reports = [
            {"accuracy": "8", "course": "Physics"},
            {"accuracy": "9", "course": "Chemistry"},
        ]
        result = service._compute_field_summary(reports)
        assert "accuracy" in result
        assert "course" in result

    def test_filters_by_specified_fields(self) -> None:
        """Verify only specified fields are included when fields param given."""
        service = self._make_service()
        reports = [
            {"accuracy": "8", "course": "Physics", "intent": "quiz"},
        ]
        result = service._compute_field_summary(reports, fields=["accuracy"])
        assert "accuracy" in result
        assert "course" not in result
        assert "intent" not in result


@pytest.mark.unit
class TestComputeGrouped:
    """Tests for _compute_grouped method (cross-tabulation)."""

    def _make_service(self) -> CustomReportService:
        return CustomReportService(session=None, customer_id=1)  # type: ignore[arg-type]

    def test_single_group_by(self) -> None:
        """Verify single-dimension grouping."""
        service = self._make_service()
        reports = [
            {"course": "Physics", "accuracy": "8"},
            {"course": "Physics", "accuracy": "9"},
            {"course": "Chemistry", "accuracy": "7"},
        ]
        groups = service._compute_grouped(reports, group_by=["course"])
        assert len(groups) == 2
        # Groups sorted by count descending
        assert groups[0]["key"] == {"course": "Physics"}
        assert groups[0]["count"] == 2
        assert groups[1]["key"] == {"course": "Chemistry"}
        assert groups[1]["count"] == 1

    def test_multi_group_by(self) -> None:
        """Verify two-dimension grouping (cross-tabulation)."""
        service = self._make_service()
        reports = [
            {"course": "Physics", "intent": "quiz", "accuracy": "8"},
            {"course": "Physics", "intent": "quiz", "accuracy": "9"},
            {"course": "Physics", "intent": "explore", "accuracy": "7"},
        ]
        groups = service._compute_grouped(reports, group_by=["course", "intent"])
        assert len(groups) == 2
        assert groups[0]["key"] == {"course": "Physics", "intent": "quiz"}
        assert groups[0]["count"] == 2

    def test_group_by_fields_excluded_from_summary(self) -> None:
        """Verify group_by fields are not in field_summary."""
        service = self._make_service()
        reports = [
            {"course": "Physics", "accuracy": "8"},
        ]
        groups = service._compute_grouped(reports, group_by=["course"])
        assert "course" not in groups[0]["field_summary"]
        assert "accuracy" in groups[0]["field_summary"]

    def test_empty_group_by_value(self) -> None:
        """Verify reports with missing group_by field use empty string."""
        service = self._make_service()
        reports = [
            {"accuracy": "8"},  # No "course" key
            {"course": "Physics", "accuracy": "9"},
        ]
        groups = service._compute_grouped(reports, group_by=["course"])
        keys = [g["key"]["course"] for g in groups]
        assert "" in keys
        assert "Physics" in keys


@pytest.mark.unit
class TestGroupBySystemDimensions:
    """Tests for group_by with system dimensions (e.g., 'test').

    System dimensions map to JOINed DB columns rather than report_data fields.
    The get_summary() method injects these values into parsed reports so that
    _compute_grouped() can use them as group keys.
    """

    def _make_service(self) -> CustomReportService:
        return CustomReportService(session=None, customer_id=1)  # type: ignore[arg-type]

    def test_group_by_test_produces_per_test_groups(self) -> None:
        """Verify group_by=['test'] produces one group per test_id."""
        service = self._make_service()
        # Simulate reports with injected "test" system dimension
        reports = [
            {"test": "147162", "accuracy": "8", "course": "Physics"},
            {"test": "147162", "accuracy": "9", "course": "Chemistry"},
            {"test": "146719", "accuracy": "7", "course": "Physics"},
        ]
        groups = service._compute_grouped(reports, group_by=["test"])
        assert len(groups) == 2
        # Groups sorted by count descending
        assert groups[0]["key"] == {"test": "147162"}
        assert groups[0]["count"] == 2
        assert groups[1]["key"] == {"test": "146719"}
        assert groups[1]["count"] == 1

    def test_group_by_test_and_course_cross_tabulation(self) -> None:
        """Verify group_by=['test', 'course'] produces cross-tabulation."""
        service = self._make_service()
        reports = [
            {"test": "147162", "course": "Physics", "accuracy": "8"},
            {"test": "147162", "course": "Physics", "accuracy": "9"},
            {"test": "147162", "course": "Chemistry", "accuracy": "7"},
            {"test": "146719", "course": "Physics", "accuracy": "6"},
        ]
        groups = service._compute_grouped(reports, group_by=["test", "course"])
        assert len(groups) == 3
        # Largest group first (test=147162, course=Physics with 2 reports)
        assert groups[0]["key"] == {"test": "147162", "course": "Physics"}
        assert groups[0]["count"] == 2

    def test_test_excluded_from_group_field_summary(self) -> None:
        """Verify 'test' is not in field_summary when used as group_by."""
        service = self._make_service()
        reports = [
            {"test": "147162", "accuracy": "8"},
        ]
        groups = service._compute_grouped(reports, group_by=["test"])
        assert "test" not in groups[0]["field_summary"]
        assert "accuracy" in groups[0]["field_summary"]

    def test_system_dimensions_excluded_from_ungrouped_field_summary(self) -> None:
        """Verify system dimension keys are excluded from ungrouped field summaries.

        Regression guard: even if 'test' is injected into report dicts,
        _compute_field_summary() must not classify it as a report field.
        """
        service = self._make_service()
        reports = [
            {"test": "147162", "accuracy": "8", "course": "Physics"},
            {"test": "146719", "accuracy": "9", "course": "Chemistry"},
        ]
        result = service._compute_field_summary(reports)
        assert "test" not in result
        assert "accuracy" in result
        assert "course" in result

    def test_system_dimensions_constant_contains_test(self) -> None:
        """Verify 'test' is in the SYSTEM_DIMENSIONS constant."""
        assert "test" in SYSTEM_DIMENSIONS
