from __future__ import annotations

from pathlib import Path

import pytest

from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.datasource.datasource import BaseDataSource
from sync_state_machine.engine.state_apply import apply_state_to_node
from sync_state_machine.pipeline.full_sync_pipeline import FullSyncPipeline
from tests.mocks.mock_datasource import MockSyncNode


class _DS(BaseDataSource):
    pass


def _set_state(pipeline: FullSyncPipeline, node: MockSyncNode, state_id: str, *, data_id: str) -> None:
    apply_state_to_node(
        pipeline.sm_runtime,
        node,
        state_id=state_id,
        reason="test setup",
        fields={"binding_status", "action", "status"},
    )
    apply_state_to_node(
        pipeline.sm_runtime,
        node,
        state_id=state_id,
        reason="test setup",
        fields={"data_id"},
        data_id=data_id,
    )


async def _new_pipeline(tmp_path: Path) -> tuple[FullSyncPipeline, DataCollection, DataCollection, BindingManager, PersistenceBackend]:
    db_path = tmp_path / "state.db"
    persistence = SQLitePersistenceBackend(str(db_path))
    await persistence.initialize()

    local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
    remote_collection = DataCollection("remote", persistence=persistence, auto_persist=False)
    binding_manager = BindingManager(persistence, auto_persist=False)

    pipeline = FullSyncPipeline(
        local_datasource=_DS(),
        remote_datasource=_DS(),
        strategies=[],
        persistence=persistence,
        local_collection=local_collection,
        remote_collection=remote_collection,
        binding_manager=binding_manager,
        node_types=["mock"],
        load_order=["mock"],
        sync_order=["mock"],
        enable_persistence=False,
    )

    return pipeline, local_collection, remote_collection, binding_manager, persistence


@pytest.mark.asyncio
async def test_peer_creating_source_moves_to_s01_when_target_create_success(tmp_path: Path) -> None:
    pipeline, local, remote, bm, persistence = await _new_pipeline(tmp_path)

    src = MockSyncNode(
        node_id="src_local",
        data_id="SRC-ID",
        data={"id": "SRC-ID", "name": "src"},
        action=SyncAction.NONE,
        status=SyncStatus.PENDING,
        binding_status=BindingStatus.UNCHECKED,
    )
    target = MockSyncNode(
        node_id="tgt_remote",
        data_id="TGT-ID",
        data={"id": "TGT-ID", "name": "tgt"},
        action=SyncAction.CREATE,
        status=SyncStatus.SUCCESS,
        binding_status=BindingStatus.NORMAL,
    )
    await local.add(src)
    await remote.add(target)

    _set_state(pipeline, src, "S13", data_id="SRC-ID")
    _set_state(pipeline, target, "S11C", data_id="TGT-ID")

    await bm.bind("mock", "src_local", "tgt_remote")

    await pipeline._finalize_peer_creating_sources("mock")

    assert src.binding_status == BindingStatus.NORMAL
    assert src.action == SyncAction.NONE
    assert src.status == SyncStatus.PENDING

    await pipeline.close()
    await persistence.close()


@pytest.mark.asyncio
async def test_peer_creating_source_stays_s13_when_target_create_failed(tmp_path: Path) -> None:
    pipeline, local, remote, bm, persistence = await _new_pipeline(tmp_path)

    src = MockSyncNode(
        node_id="src_local_fail",
        data_id="SRC-ID-FAIL",
        data={"id": "SRC-ID-FAIL", "name": "src-fail"},
        action=SyncAction.NONE,
        status=SyncStatus.PENDING,
        binding_status=BindingStatus.UNCHECKED,
    )
    target = MockSyncNode(
        node_id="tgt_remote_fail",
        data_id="",
        data={"id": "", "name": "tgt-fail"},
        action=SyncAction.CREATE,
        status=SyncStatus.FAILED,
        binding_status=BindingStatus.NORMAL,
    )
    await local.add(src)
    await remote.add(target)

    _set_state(pipeline, src, "S13", data_id="SRC-ID-FAIL")
    _set_state(pipeline, target, "S12C", data_id="")
    target.error = "remote create failed"

    await bm.bind("mock", "src_local_fail", "tgt_remote_fail")

    await pipeline._finalize_peer_creating_sources("mock")

    assert src.binding_status == BindingStatus.PEER_CREATING
    assert src.action == SyncAction.NONE
    assert src.status == SyncStatus.PENDING
    assert "peer_create_failed" in (src.error or "")

    await pipeline.close()
    await persistence.close()
