from __future__ import annotations

from types import SimpleNamespace
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.datasource.jsonl import JsonlDataSource
from sync_state_machine.pipeline.full_sync_pipeline import FullSyncPipeline


class _FakeDataSource:
    def set_state_machine_runtime(self, runtime) -> None:
        self.runtime = runtime

    def set_collection(self, collection) -> None:
        self.collection = collection

    async def load_all(self, order=None, data_type=None) -> None:
        del order, data_type

    async def sync_all(self, order=None, data_type=None, poll_async_tasks=True):
        del order, data_type, poll_async_tasks
        return {}


class _StubStrategy:
    def __init__(self, node_type: str):
        self.node_type = node_type
        self.skip_sync = False
        self.config = SimpleNamespace()
        self.sm_runtime = None

    async def bind(self):
        return None

    async def create(self):
        return []

    async def update(self):
        return []


class _FakePersistence:
    async def close(self) -> None:
        return None


@pytest.mark.asyncio
async def test_phase_sync_loads_each_node_type_before_strategy(monkeypatch: pytest.MonkeyPatch) -> None:
    project_strategy = _StubStrategy("project")
    preparation_strategy = _StubStrategy("preparation")
    contract_strategy = _StubStrategy("contract")

    pipeline = FullSyncPipeline(
        local_datasource=_FakeDataSource(),
        remote_datasource=_FakeDataSource(),
        strategies=[project_strategy, preparation_strategy, contract_strategy],
        persistence=_FakePersistence(),
        local_collection=DataCollection("local"),
        remote_collection=DataCollection("remote"),
        binding_manager=BindingManager(_FakePersistence(), auto_persist=False),
        node_types=["project", "preparation", "contract"],
        load_order=["project", "preparation", "contract"],
        sync_order=["project", "preparation", "contract"],
        enable_persistence=False,
    )

    reload_calls: list[tuple[str, str]] = []

    async def fake_commit_creates(node_type: str) -> bool:
        return node_type == "project"

    async def fake_commit_updates(node_type: str) -> bool:
        del node_type
        return False

    async def fake_load_node_type(node_type: str, *, reason: str) -> None:
        reload_calls.append((node_type, reason))

    async def fake_normalize(node_type: str) -> None:
        del node_type

    monkeypatch.setattr(pipeline, "commit_creates", fake_commit_creates)
    monkeypatch.setattr(pipeline, "commit_updates", fake_commit_updates)
    monkeypatch.setattr(pipeline, "_load_node_type", fake_load_node_type)
    monkeypatch.setattr(pipeline, "normalize_create_success_to_update_entry", fake_normalize)

    await pipeline.phase_sync_by_node_type()

    assert reload_calls == [
        ("project", "pre-strategy refresh"),
        ("project", "create wrote data"),
        ("preparation", "pre-strategy refresh"),
        ("contract", "pre-strategy refresh"),
    ]


@pytest.mark.asyncio
async def test_phase_sync_skips_reload_for_jsonl_datasources(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    project_strategy = _StubStrategy("project")

    pipeline = FullSyncPipeline(
        local_datasource=JsonlDataSource(tmp_path / "local", read_only=True),
        remote_datasource=JsonlDataSource(tmp_path / "remote", read_only=True),
        strategies=[project_strategy],
        persistence=_FakePersistence(),
        local_collection=DataCollection("local"),
        remote_collection=DataCollection("remote"),
        binding_manager=BindingManager(_FakePersistence(), auto_persist=False),
        node_types=["project"],
        load_order=["project"],
        sync_order=["project"],
        enable_persistence=False,
    )

    reload_calls: list[tuple[str, str]] = []

    async def fake_commit_creates(node_type: str) -> bool:
        return node_type == "project"

    async def fake_commit_updates(node_type: str) -> bool:
        del node_type
        return False

    async def fake_load_node_type(node_type: str, *, reason: str) -> None:
        reload_calls.append((node_type, reason))

    async def fake_normalize(node_type: str) -> None:
        del node_type

    monkeypatch.setattr(pipeline, "commit_creates", fake_commit_creates)
    monkeypatch.setattr(pipeline, "commit_updates", fake_commit_updates)
    monkeypatch.setattr(pipeline, "_load_node_type", fake_load_node_type)
    monkeypatch.setattr(pipeline, "normalize_create_success_to_update_entry", fake_normalize)

    await pipeline.phase_sync_by_node_type()

    assert reload_calls == [
        ("project", "pre-strategy refresh"),
    ]