from __future__ import annotations

from pathlib import Path

import pytest
import yaml
from pydantic import BaseModel

from sync_state_machine.config.multi_project_config import build_multi_project_config_from_file
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.sync_node import SyncNode
from sync_state_machine.pipeline.multi_project_pipeline import (
    _project_scope_overrides,
    _resolve_project_bootstrap_node_types,
)


def test_build_multi_project_config_reads_explicit_project_bootstrap_node_types(tmp_path: Path) -> None:
    config_path = tmp_path / "multi.yaml"
    config_path.write_text(
        yaml.safe_dump(
            {
                "global_config": "run_profiles/global.yaml",
                "default_project_config": "run_profiles/project.yaml",
                "project_bootstrap_node_types": ["company", "supplier"],
                "projects": [
                    {
                        "local_project_id": "local-project",
                        "remote_project_id": "remote-project",
                    }
                ],
            },
            allow_unicode=True,
            sort_keys=False,
        ),
        encoding="utf-8",
    )

    config = build_multi_project_config_from_file(project_root=tmp_path, file_path=config_path)

    assert config.project_bootstrap_node_types == ["company", "supplier"]


def test_project_scope_overrides_only_inject_project_handler_filter() -> None:
    item = type("Item", (), {"local_project_id": "local-project", "remote_project_id": "remote-project"})()

    overrides = _project_scope_overrides(item, node_types=["project", "contract", "user"])

    assert overrides == {
        "local_datasource": {
            "handler_configs": {
                "project": {"data_id_filter": ["local-project"]},
            }
        },
        "remote_datasource": {
            "handler_configs": {
                "project": {"data_id_filter": ["remote-project"]},
            }
        },
    }


def test_resolve_project_bootstrap_node_types_prefers_explicit_config() -> None:
    config = type(
        "Config",
        (),
        {"project_bootstrap_node_types": ["company", "supplier", "company"]},
    )()

    resolved = _resolve_project_bootstrap_node_types(
        config=config,
        shared_node_types=["company", "supplier", "project"],
    )

    assert resolved == ["company", "supplier"]


class _ProjectSchema(BaseModel):
    id: str


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


@pytest.mark.asyncio
async def test_binding_manager_prunes_stale_bindings_after_project_scope_cleanup() -> None:
    persistence = SQLitePersistenceBackend(":memory:")
    await persistence.initialize()
    try:
        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, scope="project_id:p1")

        local_node = _ProjectNode(node_id="local-project-1", data={"id": "p1"}, data_id="p1")
        remote_node = _ProjectNode(node_id="remote-project-1", data={"id": "p1"}, data_id="p1")
        stale_local_node = _ProjectNode(node_id="local-project-2", data={"id": "p2"}, data_id="p2")
        stale_remote_node = _ProjectNode(node_id="remote-project-2", data={"id": "p2"}, data_id="p2")

        await local_collection.add(local_node)
        await local_collection.add(stale_local_node)
        await remote_collection.add(remote_node)
        await remote_collection.add(stale_remote_node)
        await binding_manager.bind("project", "local-project-1", "remote-project-1")
        await binding_manager.bind("project", "local-project-2", "remote-project-2")

        await local_collection.filter_by_project_ids(["p1"])
        await remote_collection.filter_by_project_ids(["p1"])

        deleted = await binding_manager.prune_missing_nodes(
            local_collection=local_collection,
            remote_collection=remote_collection,
            node_types=["project"],
        )

        assert deleted == 1
        assert await binding_manager.get_remote_id("project", "local-project-1") == "remote-project-1"
        assert await binding_manager.get_remote_id("project", "local-project-2") is None
    finally:
        await persistence.close()