"""Tests for autodebug/diff.py."""

from __future__ import annotations

from autodebug.diff import diff_by_type, diff_transcripts, load_transcript


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_identical_transcripts
# ---------------------------------------------------------------------------


class TestIdenticalTranscripts:
    """No differences, identical_count matches."""

    def test_two_identical_records(self):
        left = [_rec(0, url="https://a.com"), _rec(1, url="https://b.com")]
        right = [_rec(0, url="https://a.com"), _rec(1, url="https://b.com")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False
        assert result.identical_count == 2
        assert result.first_divergence_seq is None
        assert result.differences == []

    def test_empty_transcripts(self):
        result = diff_transcripts([], [])
        assert result.has_differences is False
        assert result.identical_count == 0
        assert result.first_divergence_seq is None

    def test_single_identical_record(self):
        rec = _rec(0, method="GET", url="https://a.com")
        result = diff_transcripts([rec], [rec])
        assert result.has_differences is False
        assert result.identical_count == 1


# ---------------------------------------------------------------------------
# test_missing_record_in_right
# ---------------------------------------------------------------------------


class TestMissingRecordInRight:
    """Left has extra record not present in right."""

    def test_extra_at_end(self):
        left = [_rec(0), _rec(1)]
        right = [_rec(0)]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        assert len(result.differences) == 1
        diff = result.differences[0]
        assert diff.seq == 1
        assert diff.right_value == "<missing>"

    def test_extra_in_middle(self):
        left = [_rec(0), _rec(1), _rec(2)]
        right = [_rec(0), _rec(2)]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        missing_diffs = [d for d in result.differences if d.right_value == "<missing>"]
        assert len(missing_diffs) == 1
        assert missing_diffs[0].seq == 1

    def test_left_count_greater(self):
        left = [_rec(0), _rec(1), _rec(2)]
        right = [_rec(0)]
        result = diff_transcripts(left, right)
        assert result.left_count == 3
        assert result.right_count == 1


# ---------------------------------------------------------------------------
# test_missing_record_in_left
# ---------------------------------------------------------------------------


class TestMissingRecordInLeft:
    """Right has extra record not present in left."""

    def test_extra_at_end(self):
        left = [_rec(0)]
        right = [_rec(0), _rec(1)]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        assert len(result.differences) == 1
        diff = result.differences[0]
        assert diff.seq == 1
        assert diff.left_value == "<missing>"

    def test_right_only_record(self):
        left = []
        right = [_rec(5)]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        assert result.differences[0].seq == 5
        assert result.differences[0].left_value == "<missing>"


# ---------------------------------------------------------------------------
# test_field_differences
# ---------------------------------------------------------------------------


class TestFieldDifferences:
    """Same seq, different field values."""

    def test_different_url(self):
        left = [_rec(0, url="https://a.com")]
        right = [_rec(0, url="https://b.com")]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        url_diffs = [d for d in result.differences if d.field == "url"]
        assert len(url_diffs) == 1
        assert url_diffs[0].left_value == "https://a.com"
        assert url_diffs[0].right_value == "https://b.com"

    def test_different_method(self):
        left = [_rec(0, method="GET")]
        right = [_rec(0, method="POST")]
        result = diff_transcripts(left, right)
        method_diffs = [d for d in result.differences if d.field == "method"]
        assert len(method_diffs) == 1
        assert method_diffs[0].left_value == "GET"
        assert method_diffs[0].right_value == "POST"

    def test_multiple_field_differences(self):
        left = [_rec(0, method="GET", url="https://a.com", status=200)]
        right = [_rec(0, method="POST", url="https://b.com", status=500)]
        result = diff_transcripts(left, right)
        diff_fields = {d.field for d in result.differences}
        assert "method" in diff_fields
        assert "url" in diff_fields
        assert "status" in diff_fields

    def test_field_in_only_one_record(self):
        """A field present in left but absent in right counts as a difference."""
        left = [_rec(0, extra_field="value")]
        right = [_rec(0)]
        result = diff_transcripts(left, right)
        extras = [d for d in result.differences if d.field == "extra_field"]
        assert len(extras) == 1
        assert extras[0].left_value == "value"
        assert extras[0].right_value is None


# ---------------------------------------------------------------------------
# test_first_divergence_marked
# ---------------------------------------------------------------------------


class TestFirstDivergenceMarked:
    """Verify is_first_divergence flag set on the first difference."""

    def test_first_diff_flagged(self):
        left = [_rec(0, url="https://a.com"), _rec(1, url="https://x.com")]
        right = [_rec(0, url="https://b.com"), _rec(1, url="https://y.com")]
        result = diff_transcripts(left, right)
        assert result.first_divergence_seq == 0
        first_flagged = [d for d in result.differences if d.is_first_divergence]
        assert len(first_flagged) == 1
        assert first_flagged[0].seq == 0

    def test_later_diffs_not_flagged(self):
        left = [_rec(0), _rec(1, url="https://a.com"), _rec(2, url="https://x.com")]
        right = [_rec(0), _rec(1, url="https://b.com"), _rec(2, url="https://y.com")]
        result = diff_transcripts(left, right)
        assert result.first_divergence_seq == 1
        later = [d for d in result.differences if d.seq == 2]
        for d in later:
            assert d.is_first_divergence is False

    def test_missing_record_flagged_as_first(self):
        left = [_rec(0), _rec(1)]
        right = [_rec(0)]
        result = diff_transcripts(left, right)
        assert result.first_divergence_seq == 1
        assert result.differences[0].is_first_divergence is True


# ---------------------------------------------------------------------------
# test_volatile_fields_ignored
# ---------------------------------------------------------------------------


class TestVolatileFieldsIgnored:
    """ts, timestamp, duration, request_id not compared."""

    def test_ts_ignored(self):
        left = [_rec(0, ts="2024-01-01T00:00:00Z")]
        right = [_rec(0, ts="2024-06-15T12:30:00Z")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False
        assert result.identical_count == 1

    def test_timestamp_ignored(self):
        left = [_rec(0, timestamp=1000000)]
        right = [_rec(0, timestamp=9999999)]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_duration_ignored(self):
        left = [_rec(0, duration=0.5)]
        right = [_rec(0, duration=3.7)]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_request_id_ignored(self):
        left = [_rec(0, request_id="abc-123")]
        right = [_rec(0, request_id="xyz-789")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_non_volatile_still_detected(self):
        """Volatile fields ignored, but other field differences still caught."""
        left = [_rec(0, ts="2024-01-01T00:00:00Z", url="https://a.com")]
        right = [_rec(0, ts="2024-06-15T12:30:00Z", url="https://b.com")]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        diff_fields = {d.field for d in result.differences}
        assert "url" in diff_fields
        assert "ts" not in diff_fields

    def test_all_volatile_ignored_together(self):
        left = [_rec(0, ts="a", timestamp=1, duration=2.0, request_id="r1")]
        right = [_rec(0, ts="b", timestamp=2, duration=5.0, request_id="r2")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False
        assert result.identical_count == 1


class TestSemanticCanonicalization:
    def test_http_payload_ignores_uuid_and_timestamp_fields(self):
        left = [
            _rec(
                0,
                "HTTP",
                body={
                    "id": "123e4567-e89b-12d3-a456-426614174000",
                    "created_at": "2026-02-12T10:00:00Z",
                    "name": "alice",
                },
            )
        ]
        right = [
            _rec(
                0,
                "HTTP",
                body={
                    "id": "123e4567-e89b-12d3-a456-426614174999",
                    "created_at": "2026-02-13T11:30:00Z",
                    "name": "alice",
                },
            )
        ]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_http_payload_array_order_insensitive(self):
        left = [_rec(0, "HTTP", body={"items": [{"k": 2}, {"k": 1}]})]
        right = [_rec(0, "HTTP", body={"items": [{"k": 1}, {"k": 2}]})]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_sql_select_column_order_equivalent(self):
        left = [_rec(0, "SQL", query="SELECT a, b FROM users WHERE id = 10")]
        right = [_rec(0, "SQL", query="select b, a from users where id = 99")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_sql_structural_difference_still_detected(self):
        left = [_rec(0, "SQL", query="SELECT a FROM users WHERE id = 10")]
        right = [_rec(0, "SQL", query="SELECT a FROM users WHERE status = 'active'")]
        result = diff_transcripts(left, right)
        assert result.has_differences is True


# ---------------------------------------------------------------------------
# test_diff_by_type_groups_correctly
# ---------------------------------------------------------------------------


class TestDiffByTypeGroupsCorrectly:
    """Type-grouped diff works."""

    def test_groups_by_type(self):
        left = [
            _rec(0, "HTTP", url="https://a.com"),
            _rec(1, "SQL", query="SELECT 1"),
            _rec(2, "HTTP", url="https://c.com"),
        ]
        right = [
            _rec(0, "HTTP", url="https://b.com"),
            _rec(1, "SQL", query="SELECT 1"),
            _rec(2, "HTTP", url="https://c.com"),
        ]
        results = diff_by_type(left, right)
        assert "HTTP" in results
        assert "SQL" in results

        assert results["HTTP"].has_differences is True
        assert results["SQL"].has_differences is False

    def test_type_only_in_left(self):
        left = [_rec(0, "REDIS", key="k1")]
        right = []
        results = diff_by_type(left, right)
        assert "REDIS" in results
        assert results["REDIS"].has_differences is True
        assert results["REDIS"].left_count == 1
        assert results["REDIS"].right_count == 0

    def test_type_only_in_right(self):
        left = []
        right = [_rec(0, "MONGODB", collection="users")]
        results = diff_by_type(left, right)
        assert "MONGODB" in results
        assert results["MONGODB"].has_differences is True
        assert results["MONGODB"].left_count == 0
        assert results["MONGODB"].right_count == 1

    def test_all_types_identical(self):
        left = [_rec(0, "HTTP"), _rec(1, "SQL")]
        right = [_rec(0, "HTTP"), _rec(1, "SQL")]
        results = diff_by_type(left, right)
        for rtype, result in results.items():
            assert result.has_differences is False


# ---------------------------------------------------------------------------
# test_load_transcript_bad_lines
# ---------------------------------------------------------------------------


class TestLoadTranscriptBadLines:
    """load_transcript skips malformed JSONL lines instead of crashing."""

    def test_valid_jsonl_loads_all_records(self, tmp_path):
        f = tmp_path / "valid.jsonl"
        f.write_text('{"seq":0,"type":"HTTP"}\n{"seq":1,"type":"SQL"}\n')
        records = load_transcript(f)
        assert len(records) == 2
        assert records[0]["seq"] == 0
        assert records[1]["seq"] == 1

    def test_bad_line_skipped_good_lines_kept(self, tmp_path):
        f = tmp_path / "mixed.jsonl"
        f.write_text('{"seq":0}\n!!!bad json!!!\n{"seq":2}\n')
        records = load_transcript(f)
        assert len(records) == 2
        assert records[0]["seq"] == 0
        assert records[1]["seq"] == 2

    def test_all_bad_lines_returns_empty(self, tmp_path):
        f = tmp_path / "allbad.jsonl"
        f.write_text("not json\nalso not json\n")
        records = load_transcript(f)
        assert records == []

    def test_empty_lines_ignored(self, tmp_path):
        f = tmp_path / "blanks.jsonl"
        f.write_text('\n\n{"seq":0}\n\n')
        records = load_transcript(f)
        assert len(records) == 1

    def test_empty_file_returns_empty(self, tmp_path):
        f = tmp_path / "empty.jsonl"
        f.write_text("")
        records = load_transcript(f)
        assert records == []


# ---------------------------------------------------------------------------
# test_volatile_key_precision (Fix 1)
# ---------------------------------------------------------------------------


class TestVolatileKeyPrecision:
    """endswith('id') was overbroad — should not drop 'valid', 'grid', etc."""

    def test_valid_field_not_dropped(self):
        """'valid' should NOT be treated as volatile."""
        left = [_rec(0, valid=True)]
        right = [_rec(0, valid=False)]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        assert any(d.field == "valid" for d in result.differences)

    def test_grid_field_not_dropped(self):
        """'grid' should NOT be treated as volatile."""
        left = [_rec(0, grid="A1")]
        right = [_rec(0, grid="B2")]
        result = diff_transcripts(left, right)
        assert result.has_differences is True

    def test_paid_field_not_dropped(self):
        """'paid' should NOT be treated as volatile."""
        left = [_rec(0, paid=True)]
        right = [_rec(0, paid=False)]
        result = diff_transcripts(left, right)
        assert result.has_differences is True

    def test_id_field_still_volatile(self):
        """'id' (exact) should still be volatile."""
        left = [_rec(0, id="abc")]
        right = [_rec(0, id="xyz")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_underscore_id_field_still_volatile(self):
        """'user_id' should still be volatile."""
        left = [_rec(0, user_id="abc")]
        right = [_rec(0, user_id="xyz")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False


# ---------------------------------------------------------------------------
# test_duplicate_seq_preserved (Fix 5)
# ---------------------------------------------------------------------------


class TestDuplicateSeqPreserved:
    """Duplicate seq values should be compared pairwise, not overwritten."""

    def test_two_records_same_seq_both_compared(self):
        left = [_rec(0, url="https://a.com"), _rec(0, url="https://b.com")]
        right = [_rec(0, url="https://a.com"), _rec(0, url="https://b.com")]
        result = diff_transcripts(left, right)
        assert result.has_differences is False
        assert result.identical_count == 2

    def test_duplicate_seq_difference_detected(self):
        left = [_rec(0, url="https://a.com"), _rec(0, url="https://b.com")]
        right = [_rec(0, url="https://a.com"), _rec(0, url="https://c.com")]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        url_diffs = [d for d in result.differences if d.field == "url"]
        assert len(url_diffs) == 1
        assert url_diffs[0].left_value == "https://b.com"
        assert url_diffs[0].right_value == "https://c.com"

    def test_duplicate_seq_extra_in_left(self):
        left = [_rec(0, url="https://a.com"), _rec(0, url="https://b.com")]
        right = [_rec(0, url="https://a.com")]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        missing = [d for d in result.differences if d.right_value == "<missing>"]
        assert len(missing) == 1


# ---------------------------------------------------------------------------
# test_type_case_normalization (Fix 6)
# ---------------------------------------------------------------------------


class TestTypeCaseNormalization:
    """'sql' vs 'SQL' should not produce false diffs."""

    def test_type_case_insensitive(self):
        left = [{"type": "sql", "seq": 0, "query": "SELECT 1"}]
        right = [{"type": "SQL", "seq": 0, "query": "SELECT 1"}]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_http_case_insensitive(self):
        left = [{"type": "http", "seq": 0, "url": "https://a.com"}]
        right = [{"type": "HTTP", "seq": 0, "url": "https://a.com"}]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_mixed_case_still_detects_real_diffs(self):
        left = [{"type": "http", "seq": 0, "url": "https://a.com"}]
        right = [{"type": "HTTP", "seq": 0, "url": "https://b.com"}]
        result = diff_transcripts(left, right)
        assert result.has_differences is True
        diff_fields = {d.field for d in result.differences}
        assert "url" in diff_fields
        assert "type" not in diff_fields


# ---------------------------------------------------------------------------
# test_uuid_regex_precision (Fix 9)
# ---------------------------------------------------------------------------


class TestUUIDRegexPrecision:
    """UUID regex should match versions 1-5 only."""

    def test_v4_uuid_canonicalized(self):
        """Standard v4 UUID should be treated as volatile."""
        left = [_rec(0, "HTTP", body={"ref": "550e8400-e29b-41d4-a716-446655440000"})]
        right = [_rec(0, "HTTP", body={"ref": "123e4567-e89b-42d3-a456-426614174000"})]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_v1_uuid_canonicalized(self):
        left = [_rec(0, "HTTP", body={"ref": "550e8400-e29b-11d4-a716-446655440000"})]
        right = [_rec(0, "HTTP", body={"ref": "123e4567-e89b-12d3-a456-426614174000"})]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_version_8_not_matched(self):
        """Version 8 UUIDs should NOT be canonicalized (not a standard version)."""
        left = [_rec(0, "HTTP", body={"ref": "550e8400-e29b-81d4-a716-446655440000"})]
        right = [_rec(0, "HTTP", body={"ref": "123e4567-e89b-82d3-a456-426614174000"})]
        result = diff_transcripts(left, right)
        assert result.has_differences is True


# ---------------------------------------------------------------------------
# test_large_array_sort_cap (Fix 10)
# ---------------------------------------------------------------------------


class TestLargeArraySortCap:
    """Arrays over 500 elements skip sorting to avoid O(n log n) serialization."""

    def test_small_array_sorted(self):
        """Arrays under cap should still be order-insensitive."""
        left = [_rec(0, "HTTP", body={"items": [{"k": 2}, {"k": 1}]})]
        right = [_rec(0, "HTTP", body={"items": [{"k": 1}, {"k": 2}]})]
        result = diff_transcripts(left, right)
        assert result.has_differences is False

    def test_large_array_not_sorted(self):
        """Arrays over 500 elements should be compared as-is (order-sensitive)."""
        big_left = [{"v": i} for i in range(501)]
        big_right = list(reversed(big_left))
        left = [_rec(0, "HTTP", body={"items": big_left})]
        right = [_rec(0, "HTTP", body={"items": big_right})]
        result = diff_transcripts(left, right)
        # Order matters for large arrays, so this should show differences
        assert result.has_differences is True
