"""Core record-replay roundtrip tests.

Tests the fundamental promise of the debugging system:
record I/O during execution, replay it deterministically.
"""

from __future__ import annotations

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

import pytest


class TestHTTPRoundtrip:
    """Tests for HTTP record -> replay roundtrip."""

    def test_http_record_creates_transcript(self, tmp_path):
        """Recording HTTP creates transcript file."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        recorder.record_http({
            "method": "GET",
            "url": "https://api.example.com/users/1",
            "url_canonical": "https://api.example.com/users/<ID>",
            "status": 200,
            "response_body": '{"id": 1, "name": "Test User"}',
        })
        recorder.close()

        # Verify transcript exists
        files = list(run_dir.glob("deps_http*"))
        assert len(files) > 0

    def test_http_replay_loads_transcript(self, tmp_path):
        """Replay can load HTTP transcript."""
        from autodebug_replay.transcripts import load_transcripts

        # Create HTTP transcript
        records = [
            {
                "type": "http",
                "seq": 1,
                "method": "GET",
                "url_canonical": "https://api.example.com/users/1",
                "status": 200,
                "response_body": '{"id": 1, "name": "Test"}',
            },
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        assert index.has_more("http")
        record = index.expect("http")
        assert record["method"] == "GET"
        assert record["status"] == 200


class TestSQLRoundtrip:
    """Tests for SQL record -> replay roundtrip."""

    def test_sql_record_creates_transcript(self, tmp_path):
        """Recording SQL creates transcript file."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        recorder.record_sql({
            "query": "SELECT * FROM users WHERE id = ?",
            "params": [1],
            "rows_returned": 1,
            "rows_data": [{"id": 1, "name": "Test"}],
        })
        recorder.close()

        files = list(run_dir.glob("deps_sql*"))
        assert len(files) > 0

    def test_sql_replay_loads_transcript(self, tmp_path):
        """Replay can load SQL transcript."""
        from autodebug_replay.transcripts import load_transcripts

        records = [
            {
                "type": "sql",
                "seq": 1,
                "query": "SELECT * FROM users WHERE id = ?",
                "params": [1],
                "rows_data": [{"id": 1, "name": "Test"}],
            },
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        assert index.has_more("sql")
        record = index.expect("sql")
        assert "SELECT" in record["query"]


class TestRedisRoundtrip:
    """Tests for Redis record -> replay roundtrip."""

    def test_redis_record_creates_transcript(self, tmp_path):
        """Recording Redis creates transcript file."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        recorder.record_redis({
            "command": "GET",
            "key": "user:1",
            "result": '{"name": "Test User"}',
        })
        recorder.close()

        files = list(run_dir.glob("deps_redis*"))
        assert len(files) > 0

    def test_redis_replay_loads_transcript(self, tmp_path):
        """Replay can load Redis transcript."""
        from autodebug_replay.transcripts import load_transcripts

        records = [
            {
                "type": "redis",
                "seq": 1,
                "command": "GET",
                "key": "user:1",
                "result": '{"name": "Test User"}',
            },
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        assert index.has_more("redis")
        record = index.expect("redis")
        assert record["command"] == "GET"


class TestTimeRoundtrip:
    """Tests for time-related record -> replay."""

    def test_time_record_creates_transcript(self, tmp_path):
        """Recording time creates transcript."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        recorder.record_event({
            "type": "TIME",
            "monotonic": 1000.0,
            "time": 1704067200.0,
        })
        recorder.close()

        files = list(run_dir.glob("events*"))
        assert len(files) > 0

    def test_time_replay_loads_transcript(self, tmp_path):
        """Replay can load time transcript."""
        from autodebug_replay.transcripts import load_transcripts

        records = [
            {
                "type": "time",
                "seq": 1,
                "monotonic": 1000.0,
                "time": 1704067200.0,
            },
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        assert index.has_more("time")
        record = index.expect("time")
        assert record["monotonic"] == 1000.0


class TestEnvRoundtrip:
    """Tests for environment variable record -> replay."""

    def test_env_record_creates_transcript(self, tmp_path):
        """Recording env creates transcript."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        recorder.record_event({
            "type": "ENV",
            "key": "DATABASE_URL",
            "value": "postgresql://localhost/test",
        })
        recorder.close()

        files = list(run_dir.glob("events*"))
        assert len(files) > 0

    def test_env_replay_loads_transcript(self, tmp_path):
        """Replay can load env transcript."""
        from autodebug_replay.transcripts import load_transcripts

        records = [
            {
                "type": "env",
                "seq": 1,
                "key": "DATABASE_URL",
                "value": "postgresql://localhost/test",
            },
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        assert index.has_more("env")
        record = index.expect("env")
        assert record["key"] == "DATABASE_URL"


class TestMultipleIOTypes:
    """Tests for recording multiple I/O types together."""

    def test_multiple_types_in_transcript(self, tmp_path):
        """Recording multiple I/O types creates unified transcript."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        # Record various I/O types
        recorder.record_http({"method": "GET", "url": "https://api.example.com"})
        recorder.record_sql({"query": "SELECT 1"})
        recorder.record_redis({"command": "GET", "key": "test"})
        recorder.record_event({"type": "CUSTOM", "data": "test"})
        recorder.close()

        # Should have multiple files or unified transcript
        all_files = list(run_dir.glob("*"))
        assert len(all_files) > 0

    def test_replay_multiple_types_in_order(self, tmp_path):
        """Replay maintains order across types."""
        from autodebug_replay.transcripts import load_transcripts

        records = [
            {"type": "http", "seq": 1, "url": "https://example.com/1"},
            {"type": "sql", "seq": 2, "query": "SELECT 1"},
            {"type": "http", "seq": 3, "url": "https://example.com/2"},
            {"type": "redis", "seq": 4, "command": "GET"},
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        # Each type should maintain its own order
        http1 = index.expect("http")
        assert http1["seq"] == 1
        http2 = index.expect("http")
        assert http2["seq"] == 3

        sql = index.expect("sql")
        assert sql["seq"] == 2


class TestReplayDivergence:
    """Tests for replay divergence detection."""

    def test_missing_record_raises_divergence(self, tmp_path):
        """Missing record during replay raises divergence."""
        from autodebug_replay.transcripts import ReplayDivergence, load_transcripts

        # Empty transcript
        (tmp_path / "events.jsonl").write_text("")

        index = load_transcripts(tmp_path)

        with pytest.raises(ReplayDivergence) as exc_info:
            index.expect("http")

        assert exc_info.value.code == "MISSING_DEP"

    def test_divergence_includes_expected_type(self, tmp_path):
        """Divergence error includes what was expected."""
        from autodebug_replay.transcripts import ReplayDivergence, load_transcripts

        (tmp_path / "events.jsonl").write_text("")
        index = load_transcripts(tmp_path)

        with pytest.raises(ReplayDivergence) as exc_info:
            index.expect("sql")

        assert "sql" in str(exc_info.value).lower() or "MISSING" in str(exc_info.value)


class TestRecordingModes:
    """Tests for different recording modes."""

    def test_none_mode_skips_recording(self, tmp_path):
        """NONE mode doesn't create transcript files."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.NONE)

        recorder.record_http({"method": "GET", "url": "https://example.com"})
        recorder.record_sql({"query": "SELECT 1"})
        recorder.close()

        # In NONE mode, we don't expect transcript files
        # (implementation may vary - just check it doesn't crash)

    def test_all_mode_records_everything(self, tmp_path):
        """ALL mode records all I/O types."""
        from autodebug_probe.recorder import Recorder, RecordingMode

        run_dir = tmp_path / "run"
        run_dir.mkdir()
        recorder = Recorder(run_dir, mode=RecordingMode.ALL)

        recorder.record_http({"method": "GET", "url": "https://example.com"})
        recorder.record_sql({"query": "SELECT 1"})
        recorder.record_redis({"command": "GET", "key": "test"})
        recorder.close()

        # Should have created some files
        all_files = list(run_dir.glob("*"))
        assert len(all_files) > 0


class TestTranscriptSorting:
    """Tests for transcript ordering guarantees."""

    def test_out_of_order_records_sorted(self, tmp_path):
        """Records written out of order are sorted by seq on load."""
        from autodebug_replay.transcripts import load_transcripts

        # Write records out of sequence order
        records = [
            {"type": "http", "seq": 5, "url": "https://example.com/5"},
            {"type": "http", "seq": 1, "url": "https://example.com/1"},
            {"type": "http", "seq": 3, "url": "https://example.com/3"},
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        index = load_transcripts(tmp_path)

        # Should come out in seq order
        r1 = index.expect("http")
        assert r1["seq"] == 1
        r2 = index.expect("http")
        assert r2["seq"] == 3
        r3 = index.expect("http")
        assert r3["seq"] == 5

    def test_deterministic_across_loads(self, tmp_path):
        """Multiple loads produce identical ordering."""
        from autodebug_replay.transcripts import load_transcripts

        records = [
            {"type": "http", "seq": i, "url": f"https://example.com/{i}"}
            for i in range(20)
        ]
        (tmp_path / "events.jsonl").write_text(
            "\n".join(json.dumps(r) for r in records)
        )

        results = []
        for _ in range(5):
            index = load_transcripts(tmp_path)
            urls = []
            while index.has_more("http"):
                urls.append(index.expect("http")["url"])
            results.append(urls)

        # All loads should be identical
        for r in results[1:]:
            assert r == results[0]
