"""CRUD + serialization round-trip characterization for the repository layer.

These lock the create → get → list_all → delete contract shared by the
Target / Run / Campaign repositories (the BaseRepository skeleton), plus the
summary/plan JSON round-trip, so the generic-base refactor stays behaviour-
preserving. Scenario's bespoke create/update (binding validation, versioning)
is covered by its own tests.
"""

from datetime import datetime, timezone

from agenteval.models import (
    Campaign,
    CampaignPlanEntry,
    ChannelType,
    EvalRun,
    EvalTarget,
    PlatformType,
    RunStatus,
    RunSummary,
    TargetStatus,
    Turn,
)
from agenteval.storage.repository import (
    CampaignRepository,
    ResultRepository,
    RunRepository,
    TargetRepository,
)


def _make_target(tid: str = "t-1") -> EvalTarget:
    return EvalTarget(
        id=tid,
        name="target",
        platform=PlatformType.AI_DIGITAL_EMPLOYEE,
        channel_type=ChannelType.TUTU_API,
        channel_config={
            "base_url": "x",
            "token": "x",
            "tenant": "x",
            "chat_channel_id": "x",
            "chat_contact_id": "x",
        },
    )


def test_target_crud_round_trip(db_session):
    repo = TargetRepository(db_session)
    repo.create(_make_target())

    fetched = repo.get("t-1")
    assert fetched is not None
    assert fetched.name == "target"
    assert fetched.channel_config["base_url"] == "x"
    assert fetched.status == TargetStatus.ACTIVE or fetched.status is not None

    assert [t.id for t in repo.list_all()] == ["t-1"]

    assert repo.delete("t-1") is True
    assert repo.get("t-1") is None
    assert repo.delete("t-1") is False


def test_target_list_all_newest_first(db_session):
    repo = TargetRepository(db_session)
    older = _make_target("t-old")
    older.created_at = datetime(2026, 1, 1, tzinfo=timezone.utc)
    newer = _make_target("t-new")
    newer.created_at = datetime(2026, 6, 1, tzinfo=timezone.utc)
    repo.create(older)
    repo.create(newer)
    assert [t.id for t in repo.list_all()] == ["t-new", "t-old"]


def test_run_summary_json_round_trip(db_session):
    TargetRepository(db_session).create(_make_target())
    repo = RunRepository(db_session)
    repo.create(
        EvalRun(
            id="r-1",
            target_id="t-1",
            scenario_id="s-1",
            status=RunStatus.COMPLETED,
            summary=RunSummary(total_cases=2, passed_cases=1, pass_rate=0.5),
        )
    )
    fetched = repo.get("r-1")
    assert fetched is not None
    assert fetched.summary is not None
    assert fetched.summary.total_cases == 2
    assert fetched.summary.pass_rate == 0.5


def test_campaign_child_identity_round_trip(db_session):
    """Campaign plan identity survives the repository's model conversion seam."""
    TargetRepository(db_session).create(_make_target())
    repo = RunRepository(db_session)
    repo.create(
        EvalRun(
            id="child-1",
            target_id="t-1",
            scenario_id="s-1",
            campaign_id="campaign-1",
            campaign_plan_index=2,
            campaign_occurrence_index=1,
        )
    )

    fetched = repo.get("child-1")
    assert fetched is not None
    assert fetched.campaign_id == "campaign-1"
    assert fetched.campaign_plan_index == 2
    assert fetched.campaign_occurrence_index == 1


def test_campaign_crud_and_plan_round_trip(db_session):
    TargetRepository(db_session).create(_make_target())
    repo = CampaignRepository(db_session)
    repo.create(
        Campaign(
            id="cp-1",
            name="campaign",
            target_id="t-1",
            window_seconds=3600,
            plan=[CampaignPlanEntry(scenario_id="s-1", offset_seconds=0, count=2)],
        )
    )
    fetched = repo.get("cp-1")
    assert fetched is not None
    assert fetched.plan[0].scenario_id == "s-1"
    assert fetched.plan[0].count == 2
    assert [c.id for c in repo.list_all()] == ["cp-1"]

    # Campaign inherits the shared delete() from the base repository.
    assert repo.delete("cp-1") is True
    assert repo.get("cp-1") is None


def test_update_turn_exchange_preserves_sent_fact(db_session):
    TargetRepository(db_session).create(_make_target())
    RunRepository(db_session).create(EvalRun(id="r-1", target_id="t-1", scenario_id="s-1", status=RunStatus.RUNNING))
    repo = ResultRepository(db_session)
    repo.save_turn(
        Turn(
            id="turn-1",
            run_id="r-1",
            case_id="case-1",
            round_index=1,
            sent_message={"msgType": "text", "msgBody": {"content": "hello"}},
            sent_at=datetime(2026, 8, 6, 1, 0, tzinfo=timezone.utc),
        )
    )

    updated = repo.update_turn_exchange(
        "turn-1",
        question_msg_id="question-1",
        reply={"msgBody": {"content": "world"}},
        received_at=datetime(2026, 8, 6, 1, 0, 1, tzinfo=timezone.utc),
        latency_ms=1000,
    )

    assert updated is not None
    assert updated.get_sent_message() == {"msgType": "text", "msgBody": {"content": "hello"}}
    assert updated.case_id == "case-1"
    assert updated.round_index == 1
    assert updated.question_msg_id == "question-1"
    assert updated.get_reply() == {"msgBody": {"content": "world"}}
    assert updated.latency_ms == 1000


def test_update_turn_exchange_returns_none_for_unknown_turn(db_session):
    assert (
        ResultRepository(db_session).update_turn_exchange(
            "missing",
            question_msg_id="question-1",
            reply=None,
            received_at=None,
            latency_ms=None,
        )
        is None
    )
