from __future__ import annotations

import sqlite3
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.pipeline.full_sync_pipeline import FullSyncPipeline
from sync_state_machine.sync_system.config import OrphanAction, UpdateDirection
from tests.fixtures.mock_domain import (
    InMemoryDataSource,
    InMemoryNodeHandler,
    TestContractNode,
    TestContractSchema,
    TestContractStrategy,
    TestProjectNode,
    TestProjectSchema,
    TestProjectStrategy,
    build_contract_node,
    build_project_node,
    register_test_domain,
)


@pytest.mark.asyncio
async def test_full_pipeline_multinode_mock_persisted_db_snapshot() -> None:
    register_test_domain()

    report_dir = Path("test_results") / "sync_state_machine"
    report_dir.mkdir(parents=True, exist_ok=True)
    db_path = report_dir / "multinode_mock_pipeline.db"

    if db_path.exists():
        db_path.unlink()

    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)

    local_project_nodes = [
        build_project_node("lp_norm", "P-NORM", name="Project Normal", code="NORM"),
        build_project_node("lp_warn", "P-WARN", name="Project Warning", code="WARN"),
        build_project_node("lp_auto", "P-AUTO", name="Project Auto", code="AUTO-1"),
        build_project_node("lp_create_ok", "P-COK", name="Project Create OK", code="CREATE_OK"),
        build_project_node("lp_create_fail", "P-CFAIL", name="Project Create Fail", code="CREATE_FAIL"),
        build_project_node("lp_create_async", "P-CASYNC", name="Project Create Async", code="CREATE_ASYNC"),
        build_project_node("lp_update", "P-UPD", name="Project Update New", code="UPD-1"),
    ]
    remote_project_nodes = [
        build_project_node("rp_norm", "RP-NORM", name="Project Normal", code="NORM"),
        build_project_node("rp_warn", "RP-WARN", name="Project Warning", code="WARN", data_present=False),
        build_project_node("rp_auto", "RP-AUTO", name="Project Auto", code="AUTO-1"),
        build_project_node("rp_update", "RP-UPD", name="Project Update Old", code="UPD-1"),
    ]

    local_contract_nodes = [
        build_contract_node("lc_dep_ok", "C-DEP-OK", name="Contract Dep OK", code="C-DEP-OK", project_ref="P-NORM"),
        build_contract_node("lc_dep_err", "C-DEP-ERR", name="Contract Dep Err", code="C-DEP-ERR", project_ref="P-MISSING"),
        build_contract_node("lc_upd_skip", "C-UPD-SKIP", name="Contract Update Skip New", code="C-UPD-SKIP", project_ref="P-NORM"),
        build_contract_node("lc_upd_fail", "C-UPD-FAIL", name="Contract Update Fail New", code="C-UPD-FAIL", project_ref="P-NORM"),
    ]
    remote_contract_nodes = [
        build_contract_node("rc_upd_skip", "RC-UPD-SKIP", name="Contract Update Skip Old", code="C-UPD-SKIP", project_ref="RP-NORM"),
        build_contract_node("rc_upd_fail", "RC-UPD-FAIL", name="Contract Update Fail Old", code="C-UPD-FAIL", project_ref="RP-NORM"),
    ]

    local_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
    remote_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)

    local_ds.add_handler(
        InMemoryNodeHandler(
            node_type="test_project",
            node_class=TestProjectNode,
            schema=TestProjectSchema,
            load_nodes=local_project_nodes,
        )
    )
    local_ds.add_handler(
        InMemoryNodeHandler(
            node_type="test_contract",
            node_class=TestContractNode,
            schema=TestContractSchema,
            load_nodes=local_contract_nodes,
        )
    )

    remote_ds.add_handler(
        InMemoryNodeHandler(
            node_type="test_project",
            node_class=TestProjectNode,
            schema=TestProjectSchema,
            load_nodes=remote_project_nodes,
            create_mode_by_code={
                "CREATE_OK": "success",
                "CREATE_FAIL": "failed",
                "CREATE_ASYNC": "in_progress_success",
            },
            update_mode_by_code={"UPD-1": "success"},
        )
    )
    remote_ds.add_handler(
        InMemoryNodeHandler(
            node_type="test_contract",
            node_class=TestContractNode,
            schema=TestContractSchema,
            load_nodes=remote_contract_nodes,
            create_mode_by_code={"C-DEP-OK": "success"},
            update_mode_by_code={
                "C-UPD-SKIP": "skipped",
                "C-UPD-FAIL": "failed",
            },
        )
    )

    await binding_manager.bind("test_project", "lp_norm", "rp_norm")
    await binding_manager.bind("test_project", "lp_warn", "rp_warn")
    await binding_manager.bind("test_project", "lp_update", "rp_update")
    await binding_manager.bind("test_contract", "lc_upd_skip", "rc_upd_skip")
    await binding_manager.bind("test_contract", "lc_upd_fail", "rc_upd_fail")
    await binding_manager.persist()

    project_strategy = TestProjectStrategy("test_project", local_collection, remote_collection, binding_manager)
    project_strategy.update_config(
        auto_bind=True,
        auto_bind_fields=["code"],
        depend_fields={},
        local_orphan_action=OrphanAction.CREATE_REMOTE,
        remote_orphan_action=OrphanAction.NONE,
        update_direction=UpdateDirection.PUSH,
    )

    contract_strategy = TestContractStrategy("test_contract", local_collection, remote_collection, binding_manager)
    contract_strategy.update_config(
        auto_bind=False,
        depend_fields={"project_ref": "test_project"},
        local_orphan_action=OrphanAction.CREATE_REMOTE,
        remote_orphan_action=OrphanAction.NONE,
        update_direction=UpdateDirection.PUSH,
    )

    pipeline = FullSyncPipeline(
        local_datasource=local_ds,
        remote_datasource=remote_ds,
        strategies=[project_strategy, contract_strategy],
        node_types=["test_project", "test_contract"],
        load_order=["test_project", "test_contract"],
        sync_order=["test_project", "test_contract"],
        persistence=persistence,
        local_collection=local_collection,
        remote_collection=remote_collection,
        binding_manager=binding_manager,
        enable_persistence=True,
    )

    stats = await pipeline.run()
    assert stats["post_check_passed"] is True

    conn = sqlite3.connect(str(db_path))
    try:
        rows = conn.execute("SELECT collection_id, COUNT(*) FROM nodes GROUP BY collection_id ORDER BY collection_id").fetchall()
        total = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
    finally:
        conn.close()

    assert total >= 10
    assert any(r[0] == "local" for r in rows)
    assert any(r[0] == "remote" for r in rows)

    await persistence.close()
