"""Tests for DebugSession schema helpers."""

from __future__ import annotations

from typing import Any

from llmdebug._debug_session import (
    build_debug_session,
    coerce_crash_frame_index,
    is_debug_session_payload,
    normalize_snapshot_view,
    split_extra_context,
)


def test_split_extra_context_none() -> None:
    context, trace_payload, eval_payload = split_extra_context(None)
    assert context == {}
    assert trace_payload is None
    assert eval_payload is None


def test_split_extra_context_extracts_trace_and_eval() -> None:
    extra: dict[str, Any] = {
        "hook": "sys.excepthook",
        "trace": {"spans": [1]},
        "eval_outcome": {"status": "failed"},
    }
    context, trace_payload, eval_payload = split_extra_context(extra)
    assert context == {"hook": "sys.excepthook"}
    assert trace_payload == {"spans": [1]}
    assert eval_payload == {"status": "failed"}
    # Ensure source dict is not mutated
    assert "trace" in extra
    assert "eval_outcome" in extra


def test_build_debug_session_minimal() -> None:
    payload = build_debug_session(
        name="unit_test",
        timestamp_utc="2026-02-10T12:00:00Z",
        llmdebug_version="2.1.0",
        snapshot={"exception": {"type": "ValueError"}, "frames": []},
    )
    assert payload["schema_version"] == "2.0"
    assert payload["kind"] == "llmdebug.debug_session"
    assert payload["session"]["name"] == "unit_test"
    assert payload["snapshot"]["exception"]["type"] == "ValueError"
    assert "context" not in payload
    assert "trace" not in payload
    assert "eval" not in payload


def test_is_debug_session_payload() -> None:
    assert not is_debug_session_payload({})
    assert not is_debug_session_payload({"session": {}, "snapshot": {}})
    assert not is_debug_session_payload(
        {"kind": "other.kind", "session": {"name": "x"}, "snapshot": {"frames": []}}
    )
    assert not is_debug_session_payload({"session": {"name": "x"}, "snapshot": {"frames": []}})
    assert is_debug_session_payload(
        {"kind": "llmdebug.debug_session", "session": {"name": "x"}, "snapshot": {"frames": []}}
    )
    # Kind is optional for backward compatibility
    assert is_debug_session_payload(
        {"schema_version": "2.0", "session": {"name": "x"}, "snapshot": {"frames": []}}
    )


def test_normalize_snapshot_view_passthrough_for_legacy() -> None:
    legacy = {"schema_version": "1.0", "name": "legacy", "exception": {"type": "E"}, "frames": []}
    out = normalize_snapshot_view(legacy)
    assert out is legacy


def test_normalize_snapshot_view_envelope_context_overrides_snapshot() -> None:
    payload = {
        "schema_version": "2.0",
        "kind": "llmdebug.debug_session",
        "session": {"name": "case", "timestamp_utc": "2026-02-10T12:00:00Z"},
        "snapshot": {"exception": {"type": "E"}, "frames": [], "hook": "from_snapshot"},
        "context": {"hook": "from_context"},
    }
    out = normalize_snapshot_view(payload)
    assert out["name"] == "case"
    assert out["timestamp_utc"] == "2026-02-10T12:00:00Z"
    assert out["hook"] == "from_context"


def test_normalize_snapshot_view_fallbacks_and_extras() -> None:
    payload = {
        "schema_version": "2.0",
        "kind": "llmdebug.debug_session",
        "name": "top_name",
        "timestamp_utc": "2026-02-10T12:34:00Z",
        "llmdebug_version": "2.1.0",
        "session": {},  # Intentionally partial
        "snapshot": {"exception": {"type": "TypeError"}, "frames": []},
        "trace": {"spans": [1, 2]},
        "eval": {"status": "failed"},
        "custom_key": {"x": 1},
    }
    out = normalize_snapshot_view(payload)
    assert out["name"] == "top_name"
    assert out["timestamp_utc"] == "2026-02-10T12:34:00Z"
    assert out["llmdebug_version"] == "2.1.0"
    assert out["trace"] == {"spans": [1, 2]}
    assert out["eval"] == {"status": "failed"}
    assert out["custom_key"] == {"x": 1}


def test_coerce_crash_frame_index_handles_invalid_values() -> None:
    snapshot = {
        "frames": [
            {"function": "crash"},
            {"function": "caller"},
        ],
        "crash_frame_index": "abc",
    }
    assert coerce_crash_frame_index(snapshot) == 0


def test_coerce_crash_frame_index_falls_back_to_first_dict_frame() -> None:
    snapshot = {
        "frames": [
            "bad-frame",
            {"function": "good-frame"},
            {"function": "other-frame"},
        ],
        "crash_frame_index": 0,
    }
    assert coerce_crash_frame_index(snapshot) == 1


def test_coerce_crash_frame_index_defaults_when_frames_missing() -> None:
    snapshot = {
        "frames": None,
        "crash_frame_index": 3,
    }
    assert coerce_crash_frame_index(snapshot) == 0


def test_coerce_crash_frame_index_defaults_when_no_dict_frame_exists() -> None:
    snapshot = {
        "frames": ["bad-frame", 3, None],
        "crash_frame_index": 1,
    }
    assert coerce_crash_frame_index(snapshot) == 0
