from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, Dict, List, Optional

from ...common.types import BindingStatus, SyncAction, SyncStatus
from ...engine import e01_bootstrap

if TYPE_CHECKING:
    from ...common.binding import BindingManager
    from ...common.collection import DataCollection
    from ...engine.dispatcher import StateMachineRuntime

logger = logging.getLogger(__name__)


def get_phase1_reset_defaults() -> Dict[str, Any]:
    return {
        "binding_status": BindingStatus.UNCHECKED,
        "error": None,
    }


async def _resolve_local_id_for_unbind(
    *,
    node_type: str,
    any_side_node_id: str,
    binding_manager: "BindingManager",
) -> Optional[str]:
    remote_id = await binding_manager.get_remote_id(node_type, any_side_node_id)
    if remote_id:
        return any_side_node_id
    local_id = await binding_manager.get_local_id(node_type, any_side_node_id)
    if local_id:
        return local_id
    return None


def _is_create_zombie(node) -> bool:
    if node.action != SyncAction.CREATE:
        return False
    if node.status == SyncStatus.FAILED:
        return True
    return not bool(node.data_id)


async def _cleanup_one_side(
    *,
    node_type: str,
    collection: "DataCollection",
    label: str,
    cleaned_ids: List[str],
    binding_manager: "BindingManager",
    runtime: "StateMachineRuntime",
) -> None:
    nodes = collection.filter(node_type=node_type)
    for node in nodes:
        if collection.is_bootstrap_blocked(node):
            logger.debug(
                f"[{node_type}] Skip bootstrap-blocked node during E01 cleanup: node_id={node.node_id}, error={node.error}"
            )
            continue

        decision = e01_bootstrap(runtime, node=node, is_create_zombie=_is_create_zombie(node))
        if decision.to_state != "S15":
            continue

        local_id_for_unbind = await _resolve_local_id_for_unbind(
            node_type=node_type,
            any_side_node_id=node.node_id,
            binding_manager=binding_manager,
        )

        if local_id_for_unbind:
            try:
                await binding_manager.unbind(node_type, local_id_for_unbind)
                bind_note = f"unbound(local_id={local_id_for_unbind})"
            except Exception as exc:
                bind_note = f"unbind_failed(local_id={local_id_for_unbind}, err={exc})"
        else:
            bind_note = "no_binding"

        reason = "status=FAILED" if node.status == SyncStatus.FAILED else "data_id empty"
        logger.info(
            f"[{node_type}] Cleaning up CREATE zombie ({label}): "
            f"node_id={node.node_id}, reason={reason}, {bind_note}"
        )

        await collection.delete(node.node_id)
        cleaned_ids.append(node.node_id)


async def run_phase2_cleanup(
    *,
    node_types: List[str],
    local_collection: "DataCollection",
    remote_collection: "DataCollection",
    binding_manager: "BindingManager",
    runtime: "StateMachineRuntime",
) -> int:
    total_cleaned = 0

    for node_type in node_types:
        cleaned_local: List[str] = []
        cleaned_remote: List[str] = []
        await _cleanup_one_side(
            node_type=node_type,
            collection=local_collection,
            label="local",
            cleaned_ids=cleaned_local,
            binding_manager=binding_manager,
            runtime=runtime,
        )
        await _cleanup_one_side(
            node_type=node_type,
            collection=remote_collection,
            label="remote",
            cleaned_ids=cleaned_remote,
            binding_manager=binding_manager,
            runtime=runtime,
        )

        cleaned_count = len(cleaned_local) + len(cleaned_remote)
        total_cleaned += cleaned_count
        if cleaned_count > 0:
            logger.warning(
                f"[{node_type}] Cleaned up CREATE-failed zombies: "
                f"local_deleted={len(cleaned_local)}, remote_deleted={len(cleaned_remote)}"
            )

    return total_cleaned
