"""Tests for autodebug/minimize.py."""

from __future__ import annotations

from typing import Any, Dict, List

from autodebug.minimize import ddmin, hierarchical_ddmin, slice_by_causality


def _rec(seq: int, record_type: str = "HTTP", **fields) -> dict:
    """Build a minimal transcript record."""
    r = {"type": record_type, "seq": seq}
    r.update(fields)
    return r


# ---------------------------------------------------------------------------
# test_ddmin_reduces_transcript
# ---------------------------------------------------------------------------


class TestDdminReducesTranscript:
    """ddmin removes unnecessary records."""

    def test_removes_irrelevant_records(self):
        """Only record at seq=3 is needed; the others can be dropped."""
        essential = _rec(3, "HTTP", url="https://fail.com")
        records = [_rec(0), _rec(1), _rec(2), essential, _rec(4), _rec(5)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return any(r.get("seq") == 3 for r in subset)

        result = ddmin(records, test_fn)
        assert len(result) < len(records)
        assert any(r.get("seq") == 3 for r in result)

    def test_reduces_ten_to_two(self):
        """Out of 10 records, only seq 2 and 7 are essential."""
        records = [_rec(i) for i in range(10)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            seqs = {r["seq"] for r in subset}
            return 2 in seqs and 7 in seqs

        result = ddmin(records, test_fn)
        result_seqs = {r["seq"] for r in result}
        assert 2 in result_seqs
        assert 7 in result_seqs
        assert len(result) <= 4  # ddmin should get close to 2

    def test_all_records_needed(self):
        """When all records are needed, nothing is removed."""
        records = [_rec(i) for i in range(3)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            seqs = {r["seq"] for r in subset}
            return seqs == {0, 1, 2}

        result = ddmin(records, test_fn)
        assert len(result) == 3


# ---------------------------------------------------------------------------
# test_ddmin_preserves_minimum
# ---------------------------------------------------------------------------


class TestDdminPreservesMinimum:
    """Result still triggers test_fn."""

    def test_result_passes_test_fn(self):
        records = [_rec(i) for i in range(8)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return any(r.get("seq") == 5 for r in subset)

        result = ddmin(records, test_fn)
        assert test_fn(result) is True

    def test_result_is_subset_of_original(self):
        records = [_rec(i, url=f"https://{i}.com") for i in range(6)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return any(r.get("seq") == 0 for r in subset)

        result = ddmin(records, test_fn)
        original_seqs = {r["seq"] for r in records}
        result_seqs = {r["seq"] for r in result}
        assert result_seqs.issubset(original_seqs)


# ---------------------------------------------------------------------------
# test_ddmin_single_record
# ---------------------------------------------------------------------------


class TestDdminSingleRecord:
    """Single record transcript returns it."""

    def test_single_record_returned(self):
        records = [_rec(0, url="https://only.com")]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return len(subset) > 0

        result = ddmin(records, test_fn)
        assert len(result) == 1
        assert result[0]["seq"] == 0

    def test_single_record_failing(self):
        """Even if test_fn would accept empty, ddmin loop exits with the record."""
        records = [_rec(0)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return True  # always passes

        result = ddmin(records, test_fn)
        # ddmin can't go below 1 when len(transcript) == 1, loop terminates
        assert len(result) >= 1


# ---------------------------------------------------------------------------
# test_hierarchical_minimizes_by_type
# ---------------------------------------------------------------------------


class TestHierarchicalMinimizesByType:
    """HTTP minimized before SQL.

    Note: hierarchical_ddmin processes HTTP first (Phase 1), then SQL
    (Phase 2), then other dep types (Phase 3), then time/rng (Phase 4).
    """

    def test_removes_unnecessary_http_keeps_sql(self):
        """Failure depends only on SQL record; HTTP can be removed.

        We verify the key contract: test_fn still passes and the
        essential SQL record is present in the result.
        """
        records = [
            _rec(0, "HTTP", url="https://a.com"),
            _rec(1, "HTTP", url="https://b.com"),
            _rec(2, "SQL", query="INSERT INTO t VALUES(1)"),
            _rec(3, "HTTP", url="https://c.com"),
        ]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return any(r.get("type") == "SQL" and r.get("seq") == 2 for r in subset)

        result = hierarchical_ddmin(records, test_fn)
        assert test_fn(result) is True
        sql_count = sum(1 for r in result if r.get("type") == "SQL")
        assert sql_count >= 1  # SQL must be kept

    def test_preserves_test_fn_across_phases(self):
        """After hierarchical minimization the test function still holds."""
        records = [
            _rec(0, "HTTP"),
            _rec(1, "HTTP"),
            _rec(2, "SQL"),
            _rec(3, "SQL"),
            _rec(4, "HTTP"),
            _rec(5, "SQL"),
        ]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            seqs = {r["seq"] for r in subset}
            return 1 in seqs and 3 in seqs

        result = hierarchical_ddmin(records, test_fn)
        assert test_fn(result) is True
        result_seqs = {r["seq"] for r in result}
        assert 1 in result_seqs
        assert 3 in result_seqs

    def test_result_sorted_by_seq(self):
        """hierarchical_ddmin sorts result by seq."""
        records = [
            _rec(0, "HTTP"),
            _rec(1, "SQL"),
            _rec(2, "HTTP"),
            _rec(3, "SQL"),
        ]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return len(subset) >= 1

        result = hierarchical_ddmin(records, test_fn)
        seqs = [r["seq"] for r in result]
        assert seqs == sorted(seqs)

    def test_only_http_records(self):
        """When transcript has only HTTP records, hierarchical still works."""
        records = [_rec(i, "HTTP") for i in range(5)]

        def test_fn(subset: List[Dict[str, Any]]) -> bool:
            return any(r.get("seq") == 2 for r in subset)

        result = hierarchical_ddmin(records, test_fn)
        assert test_fn(result) is True


# ---------------------------------------------------------------------------
# test_slice_by_causality_keeps_nearby
# ---------------------------------------------------------------------------


class TestSliceByCausalityKeepsNearby:
    """Records near failure_seq kept."""

    def test_records_within_window(self):
        records = [_rec(i, "HTTP", url=f"https://{i}.com") for i in range(50)]
        result = slice_by_causality(records, failure_seq=40, window_size=10)
        result_seqs = {r["seq"] for r in result}
        # Records from seq 30 through 40 should be included
        for s in range(30, 41):
            assert s in result_seqs

    def test_records_beyond_window_excluded(self):
        records = [_rec(i, "HTTP", url=f"https://{i}.com") for i in range(50)]
        result = slice_by_causality(records, failure_seq=40, window_size=5)
        result_seqs = {r["seq"] for r in result}
        # Records far before the window should be excluded (unless causally related)
        for s in range(0, 30):
            # These have unique URLs, so no causal link
            assert s not in result_seqs

    def test_records_after_failure_excluded(self):
        """Records after the failure_seq are not included."""
        records = [_rec(i) for i in range(50)]
        result = slice_by_causality(records, failure_seq=40, window_size=10)
        result_seqs = {r["seq"] for r in result}
        for s in range(41, 50):
            assert s not in result_seqs

    def test_small_window_size(self):
        records = [_rec(i) for i in range(20)]
        result = slice_by_causality(records, failure_seq=15, window_size=3)
        result_seqs = {r["seq"] for r in result}
        for s in range(12, 16):
            assert s in result_seqs


# ---------------------------------------------------------------------------
# test_slice_by_causality_keeps_related
# ---------------------------------------------------------------------------


class TestSliceByCausalityKeepsRelated:
    """Records with same URL/table kept even outside window."""

    def test_earlier_http_with_same_url_included(self):
        """An HTTP record outside the window but sharing a URL with a window record."""
        shared_url = "https://api.example.com/users"
        records = [
            _rec(0, "HTTP", url_canonical=shared_url),       # outside window, but same URL
            _rec(1, "HTTP", url_canonical="https://other.com"),  # outside window, different URL
            _rec(2, "HTTP", url_canonical="https://other2.com"),
            _rec(3, "HTTP", url_canonical="https://other3.com"),
            _rec(4, "HTTP", url_canonical="https://other4.com"),
            _rec(5, "HTTP", url_canonical="https://other5.com"),
            _rec(6, "HTTP", url_canonical="https://other6.com"),
            _rec(7, "HTTP", url_canonical="https://other7.com"),
            _rec(8, "HTTP", url_canonical="https://other8.com"),
            _rec(9, "HTTP", url_canonical=shared_url),       # inside window
            _rec(10, "HTTP", url_canonical="https://done.com"),  # failure
        ]
        result = slice_by_causality(records, failure_seq=10, window_size=3)
        result_seqs = {r["seq"] for r in result}

        # seq 0 shares URL with seq 9 (in window), so should be pulled in
        assert 0 in result_seqs
        # seq 1 has different URL, outside window, should be excluded
        assert 1 not in result_seqs

    def test_earlier_sql_with_same_table_included(self):
        """A SQL record outside the window but touching the same table."""
        records = [
            _rec(0, "SQL", query="INSERT INTO users VALUES(1)"),   # same table
            _rec(1, "SQL", query="SELECT * FROM orders"),          # different table
            _rec(2, "SQL", query="SELECT * FROM products"),
            _rec(3, "SQL", query="SELECT * FROM products"),
            _rec(4, "SQL", query="SELECT * FROM products"),
            _rec(5, "SQL", query="SELECT * FROM products"),
            _rec(6, "SQL", query="SELECT * FROM products"),
            _rec(7, "SQL", query="SELECT * FROM products"),
            _rec(8, "SQL", query="SELECT * FROM users WHERE id=1"),  # inside window, same table
            _rec(9, "SQL", query="SELECT 1"),                       # inside window
            _rec(10, "HTTP", url_canonical="https://fail.com"),     # failure
        ]
        result = slice_by_causality(records, failure_seq=10, window_size=5)
        result_seqs = {r["seq"] for r in result}

        # seq 0 touches "users" table, same as seq 8 in window
        assert 0 in result_seqs
        # seq 1 touches "orders", not referenced in window
        assert 1 not in result_seqs

    def test_no_causal_link_no_expansion(self):
        """When window records don't share resources, nothing outside window is added."""
        records = [
            _rec(0, "HTTP", url_canonical="https://unique0.com"),
            _rec(1, "HTTP", url_canonical="https://unique1.com"),
            _rec(8, "HTTP", url_canonical="https://unique8.com"),
            _rec(9, "HTTP", url_canonical="https://unique9.com"),
            _rec(10, "HTTP", url_canonical="https://unique10.com"),
        ]
        result = slice_by_causality(records, failure_seq=10, window_size=3)
        result_seqs = {r["seq"] for r in result}
        # Window: 7..10.  seq 8,9,10 inside.  seq 0,1 outside and no URL overlap.
        assert 0 not in result_seqs
        assert 1 not in result_seqs
