from __future__ import annotations

from typing import Any, cast

import pytest
from pydantic import BaseModel

from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.sync_node import SyncNode
from sync_state_machine.common.types import BindingStatus, SyncStatus
from sync_state_machine.common.types import SyncAction
from sync_state_machine.datasource.api.datasource import ApiDataSource
from sync_state_machine.datasource.datasource import BaseDataSource
from sync_state_machine.datasource.task_result import TaskResult


class _ProjectSchema(BaseModel):
    id: str
    name: str


class _ProjectNode(SyncNode[_ProjectSchema]):
    node_type = "project"
    schema = _ProjectSchema


class _Handler:
    node_type = "project"


class _DS(BaseDataSource):
    pass


def _build_node(*, node_id: str, data_id: str, name: str) -> _ProjectNode:
    return _ProjectNode(node_id=node_id, data_id=data_id, data={"id": data_id, "name": name})


def test_api_datasource_resolve_push_id_from_put_trace() -> None:
    ds = ApiDataSource(base_url="http://example.com", uid="u", secret="s")
    node = _build_node(node_id="n1", data_id="biz-1", name="A")
    with node.allow_core_state_write("test"):
        node.action = SyncAction.UPDATE

    ds.client._record_trace(
        method="PUT",
        endpoint="/project",
        url="http://example.com/project",
        request_params=None,
        request_payload={"push_id": "push-123", "biz_id": "biz-1", "data": {"name": "B"}},
        response={"code": 200},
        elapsed_ms=8,
    )

    push_id = ds._resolve_push_id_for_success(node, TaskResult.success(node_id=node.node_id))
    assert push_id == "push-123"


@pytest.mark.asyncio
async def test_upsert_loaded_nodes_append_load_source_log() -> None:
    collection = DataCollection("remote")
    ds = _DS()
    ds.set_collection(collection)

    existing = _build_node(node_id="n1", data_id="p-1", name="old")
    existing.context["_loaded_from_persistence"] = True
    await collection.add(existing)

    incoming = _build_node(node_id="n2", data_id="p-1", name="new")
    await ds._upsert_loaded_nodes(cast(Any, _Handler()), "project", [incoming])

    updated = collection.get_by_data_id("project", "p-1")
    assert updated is not None
    assert updated.sync_log is not None
    assert "加载来源: persistence -> _DS.load 覆盖刷新" in updated.sync_log


@pytest.mark.asyncio
async def test_api_datasource_append_trace_on_failed_result() -> None:
    collection = DataCollection("remote")
    ds = ApiDataSource(base_url="http://example.com", uid="u", secret="s")
    ds.set_collection(collection)

    node = _build_node(node_id="n-fail", data_id="biz-fail", name="X")
    with node.allow_core_state_write("test"):
        node.binding_status = BindingStatus.NORMAL
        node.action = SyncAction.UPDATE
        node.status = SyncStatus.IN_PROGRESS
    await collection.add(node)

    ds.client._record_trace(
        method="PUT",
        endpoint="/contract/update",
        url="http://example.com/contract/update",
        request_params=None,
        request_payload={"push_id": "push-fail", "biz_id": "biz-fail", "data": {"name": "Y"}},
        response={"code": 500, "message": "bad supplier"},
        elapsed_ms=15,
    )

    await ds._handle_task_result(
        cast(Any, _Handler()),
        TaskResult.failed(node_id=node.node_id, error="Update failed: bad supplier"),
        {},
    )

    updated = collection.get(node.node_id)
    assert updated is not None
    assert updated.sync_log is not None
    assert "API链路[submit-failed] PUT /contract/update" in updated.sync_log
    assert "response={\"code\":500,\"message\":\"bad supplier\"}" in updated.sync_log
    assert updated.sync_log.index("API链路[submit-failed] PUT /contract/update") < updated.sync_log.index("执行状态写回")


@pytest.mark.asyncio
async def test_api_datasource_failed_update_does_not_use_unrelated_op_trace() -> None:
    collection = DataCollection("remote")
    ds = ApiDataSource(base_url="http://example.com", uid="u", secret="s")
    ds.set_collection(collection)

    node = _build_node(node_id="n-miss", data_id="biz-target", name="X")
    with node.allow_core_state_write("test"):
        node.binding_status = BindingStatus.NORMAL
        node.action = SyncAction.UPDATE
        node.status = SyncStatus.IN_PROGRESS
    await collection.add(node)

    ds.client._record_trace(
        method="PUT",
        endpoint="/material/plan",
        url="http://example.com/material/plan",
        request_params=None,
        request_payload={"push_id": "push-other", "biz_id": "biz-other", "data": {"k": 1}},
        response={"code": 200, "message": "OK"},
        elapsed_ms=20,
    )

    await ds._handle_task_result(
        cast(Any, _Handler()),
        TaskResult.failed(node_id=node.node_id, error="Validation failed"),
        {},
    )

    updated = collection.get(node.node_id)
    assert updated is not None
    assert updated.sync_log is not None
    assert "API链路[submit-failed] PUT /material/plan" not in updated.sync_log
    assert "未找到匹配 biz_id 的请求记录" in updated.sync_log
