from __future__ import annotations

import json
from dataclasses import dataclass

from synaptopus.runtime import StepTrace, run_until_acceptance_count
from synaptopus.serialization import (
    deserialize_execution_record,
    save_execution_record_json,
    serialize_execution_record,
)


@dataclass(frozen=True)
class SequenceState:
    accepted: tuple[int, ...] = ()
    attempts: int = 0


class EvenAcceptanceSystem:
    def step(self, state: SequenceState) -> StepTrace[SequenceState, int, dict[str, int]]:
        candidate = state.attempts + 1
        accepted = candidate % 2 == 0
        next_state = SequenceState(
            accepted=state.accepted + ((candidate,) if accepted else ()),
            attempts=state.attempts + 1,
        )
        return StepTrace(
            previous_state=state,
            next_state=next_state,
            candidate=candidate,
            accepted=accepted,
            elapsed_seconds=0.0,
            metadata={"attempt": state.attempts + 1},
        )


def test_execution_record_serialization_converts_dataclasses() -> None:
    record = run_until_acceptance_count(
        EvenAcceptanceSystem(),
        SequenceState(),
        accepted_count=2,
        max_attempts_per_accept=4,
    )

    serialized = serialize_execution_record(record)

    assert serialized.final_state["accepted"] == [2, 4]
    assert serialized.attempts[0].previous_state["attempts"] == 0
    assert serialized.attempts[1].candidate == 2
    assert serialized.accepted[0].metadata["attempt"] == 2


def test_execution_record_serialization_saves_json(tmp_path) -> None:
    record = run_until_acceptance_count(
        EvenAcceptanceSystem(),
        SequenceState(),
        accepted_count=1,
        max_attempts_per_accept=4,
    )
    destination = tmp_path / "trace.json"

    save_execution_record_json(record, destination)
    loaded = json.loads(destination.read_text(encoding="utf-8"))

    assert loaded["artifact_type"] == "execution_trace"
    assert loaded["payload"]["accepted"][0]["candidate"] == 2
    assert loaded["payload"]["final_state"]["accepted"] == [2]


def test_execution_record_deserialization_restores_typed_record() -> None:
    record = run_until_acceptance_count(
        EvenAcceptanceSystem(),
        SequenceState(),
        accepted_count=2,
        max_attempts_per_accept=4,
    )

    serialized = serialize_execution_record(record)
    restored = deserialize_execution_record(
        serialized,
        state_decoder=lambda data: SequenceState(
            accepted=tuple(int(value) for value in data["accepted"]),
            attempts=int(data["attempts"]),
        ),
        candidate_decoder=lambda data: int(data),
        metadata_decoder=lambda data: None if data is None else {"attempt": int(data["attempt"])},
    )

    assert restored.final_state == record.final_state
    assert tuple(step.candidate for step in restored.accepted) == (2, 4)
    assert restored.attempts[0].metadata == {"attempt": 1}
