"""Cluster lifecycle integration — wires sync scheduler to node state machine.

When a node transitions to SPOKE, the sync scheduler starts.
When it leaves SPOKE (to LEAVING or ERROR), the scheduler stops.
Health checks run on each sync cycle and can trigger ERROR transitions.

Usage:
    from greymatter_core.cluster.lifecycle import bind_sync_to_state_machine

    sm = NodeStateMachine()
    scheduler = SyncScheduler(local_db, remote_db)
    health_checker = HealthChecker(local_db)

    bind_sync_to_state_machine(sm, scheduler, health_checker)

    # Now state transitions automatically start/stop sync
    sm.transition(NodeState.JOINING)
    sm.transition(NodeState.SPOKE)  # scheduler starts
    sm.transition(NodeState.LEAVING)  # scheduler stops
"""
from __future__ import annotations

import asyncio
import logging
from typing import Any

from greymatter_core.cluster.health import HealthChecker, HealthStatus
from greymatter_core.cluster.state import NodeState, NodeStateMachine
from greymatter_core.sync.scheduler import SyncScheduler

logger = logging.getLogger(__name__)


def bind_sync_to_state_machine(
    sm: NodeStateMachine,
    scheduler: SyncScheduler,
    health_checker: HealthChecker | None = None,
    *,
    health_on_sync: bool = True,
) -> None:
    """Wire the sync scheduler lifecycle to node state transitions.

    Args:
        sm: Node state machine.
        scheduler: Sync scheduler instance.
        health_checker: Optional health checker. If provided and
            health_on_sync is True, runs health checks after each
            sync cycle.
        health_on_sync: Whether to run health checks after each sync.
    """

    def _on_enter_spoke(old: NodeState, new: NodeState) -> None:
        """Start sync when becoming a spoke."""
        logger.info("Entered SPOKE state — starting sync scheduler")
        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                asyncio.ensure_future(scheduler.start())
            else:
                loop.run_until_complete(scheduler.start())
        except RuntimeError:
            # No event loop — create one (non-async context)
            loop = asyncio.new_event_loop()
            loop.run_until_complete(scheduler.start())

    def _on_exit_spoke(old: NodeState, new: NodeState) -> None:
        """Stop sync when leaving spoke state."""
        logger.info("Leaving SPOKE state — stopping sync scheduler")
        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                asyncio.ensure_future(scheduler.stop())
            else:
                loop.run_until_complete(scheduler.stop())
        except RuntimeError:
            loop = asyncio.new_event_loop()
            loop.run_until_complete(scheduler.stop())

    sm.on_enter(NodeState.SPOKE, _on_enter_spoke)
    sm.on_exit(NodeState.SPOKE, _on_exit_spoke)

    # Wire health checks to sync callbacks
    if health_checker and health_on_sync:
        original_callback = scheduler.on_sync_complete

        def _health_after_sync(result: Any) -> None:
            if original_callback:
                original_callback(result)

            report = health_checker.check_node()
            if report.status == HealthStatus.UNHEALTHY:
                logger.error(
                    "Node unhealthy after sync: %s",
                    [c.message for c in report.checks if c.status == HealthStatus.UNHEALTHY],
                )
                # Trigger error state if still in SPOKE
                if sm.state == NodeState.SPOKE and sm.can_transition(NodeState.ERROR):
                    sm.transition(
                        NodeState.ERROR,
                        error_info=f"Health check failed: {report.checks}",
                    )

        scheduler.on_sync_complete = _health_after_sync

    logger.debug("Sync scheduler bound to state machine lifecycle")
