"""Route bridging between stream and graph routes (actor-first)."""

from __future__ import annotations

import asyncio
import logging
from typing import Any

from rx.scheduler.eventloop import AsyncIOScheduler  # type: ignore[attr-defined]

from cleveragents.langgraph.graph import GraphConfig, LangGraph
from cleveragents.langgraph.nodes import Edge, NodeConfig, NodeType
from cleveragents.langgraph.state import GraphState
from cleveragents.reactive.route import RouteConfig, RouteType
from cleveragents.reactive.stream_router import (
    ReactiveStreamRouter,
    StreamConfig,
    StreamMessage,
    StreamType,
)


class RouteBridge:
    """Convert between stream and graph routes based on conditions."""

    def __init__(
        self,
        stream_router: ReactiveStreamRouter,
        agents: Any,
        scheduler: AsyncIOScheduler | None = None,
    ):
        self.stream_router = stream_router
        # Accept either a dict of Agent instances or any registry-like object;
        # fallback to empty dict.
        if isinstance(agents, dict):
            self.agents = agents
        else:
            try:
                # Try to get registry map method
                self.agents = {
                    actor.name: actor
                    for actor in agents.list_actors()  # type: ignore[attr-defined]
                }
            except Exception:  # pylint: disable=broad-except
                self.agents = {}
        self.scheduler = scheduler
        self.logger = logging.getLogger(__name__)
        self._active_conversions: dict[str, Any] = {}

    async def check_upgrade_conditions(
        self, route_config: RouteConfig, message: StreamMessage
    ) -> bool:
        if not route_config.bridge or route_config.type != RouteType.STREAM:
            return False
        conditions = route_config.bridge.upgrade_conditions
        if "needs_state" in conditions and message.metadata.get(
            "requires_state", False
        ):
            return True
        if "message_count" in conditions and (
            self._get_message_count(route_config.name) >= conditions["message_count"]
        ):
            return True
        if "complexity_threshold" in conditions:
            from cleveragents.reactive.route import (
                RouteComplexityAnalyzer,  # pylint: disable=import-outside-toplevel
            )

            analysis = RouteComplexityAnalyzer.analyze_route(route_config)
            if analysis["score"] >= conditions["complexity_threshold"]:
                return True
        if "custom_predicate" in conditions:
            predicate = conditions["custom_predicate"]
            if callable(predicate) and predicate(message, route_config):
                return True
        return False

    async def check_downgrade_conditions(
        self, route_config: RouteConfig, state: GraphState
    ) -> bool:
        if not route_config.bridge or route_config.type != RouteType.GRAPH:
            return False
        conditions = route_config.bridge.downgrade_conditions
        if "idle_time" in conditions:
            idle_threshold = conditions["idle_time"]
            last_updated = state.metadata.get("last_updated")
            if (
                last_updated
                and (asyncio.get_event_loop().time() - last_updated) > idle_threshold
            ):
                return True
        if (
            "state_size" in conditions
            and len(state.messages) <= conditions["state_size"]
        ):
            return True
        return bool(
            "no_conditionals_used" in conditions
            and not state.metadata.get("conditional_edges_used", False)
        )

    def add_route(self, route_config: RouteConfig) -> None:
        """Register a graph route for bridging.

        For now we store the route config; LangGraph graph creation is handled
        when upgrade/downgrade is invoked.
        """
        self._active_conversions[route_config.name] = {
            "type": "graph",
            "instance": None,
            "original_config": route_config,
        }

    async def upgrade_stream_to_graph(
        self, route_config: RouteConfig, message: StreamMessage
    ) -> LangGraph:
        self.logger.info("Upgrading stream route '%s' to graph", route_config.name)
        graph_config = self._create_graph_from_stream(route_config)
        initial_state: dict[str, Any] = {}
        if route_config.bridge and route_config.bridge.state_extractor:
            extractor = route_config.bridge.state_extractor
            if callable(extractor):
                initial_state = extractor(message)
        graph = LangGraph(
            config=graph_config,
            agents=self.agents,
            stream_router=self.stream_router,
            scheduler=self.scheduler,
        )
        if initial_state:
            graph.state_manager.update_state(initial_state)
        if route_config.bridge and route_config.bridge.preserve_subscriptions:
            for _ in route_config.subscriptions:
                pass
        self._active_conversions[route_config.name] = {
            "type": "graph",
            "instance": graph,
            "original_config": route_config,
        }
        return graph

    async def downgrade_graph_to_stream(
        self, route_config: RouteConfig, graph: LangGraph
    ) -> StreamConfig:
        self.logger.info("Downgrading graph route '%s' to stream", route_config.name)
        stream_config = self._create_stream_from_graph(route_config, graph)
        if route_config.bridge and route_config.bridge.state_flattener:
            flattener = route_config.bridge.state_flattener
            if callable(flattener):
                _ = flattener(graph.state_manager.get_state())
        if (
            route_config.bridge
            and route_config.bridge.preserve_checkpointing
            and graph.state_manager.checkpoint_dir
        ):
            graph.state_manager._save_checkpoint()  # pylint: disable=protected-access
        self._active_conversions[route_config.name] = {
            "type": "stream",
            "instance": stream_config,
            "original_config": route_config,
        }
        return stream_config

    def _create_graph_from_stream(self, route_config: RouteConfig) -> GraphConfig:
        nodes: dict[str, NodeConfig] = {}
        edges: list[Edge] = []
        prev_node = "start"
        for i, operator in enumerate(route_config.operators):
            node_name = f"op_{i}_{operator.get('type', 'unknown')}"
            if operator["type"] == "map" and "agent" in operator.get("params", {}):
                nodes[node_name] = NodeConfig(
                    name=node_name,
                    type=NodeType.AGENT,
                    agent=operator["params"]["agent"],
                )
            else:
                nodes[node_name] = NodeConfig(
                    name=node_name,
                    type=NodeType.FUNCTION,
                    function=f"stream_operator_{operator.get('type', 'unknown')}",
                )
            edges.append(Edge(source=prev_node, target=node_name))
            prev_node = node_name
        edges.append(Edge(source=prev_node, target="end"))
        return GraphConfig(
            name=f"{route_config.name}_graph",
            nodes=nodes,
            edges=edges,
            entry_point="start",
            checkpointing=True,
            parallel_execution=False,
        )

    def _create_stream_from_graph(
        self, route_config: RouteConfig, graph: LangGraph
    ) -> StreamConfig:
        operators: list[dict[str, Any]] = []
        agents: list[str] = []
        topological_levels = graph._topological_levels()  # pylint: disable=protected-access
        sorted_nodes: list[str] = []
        for level in sorted(topological_levels.keys()):
            sorted_nodes.extend(sorted(topological_levels[level]))
        for node_name in sorted_nodes:
            if node_name in ("start", "end"):
                continue
            node = graph.nodes.get(node_name)
            if not node:
                continue
            cfg = node.config if hasattr(node, "config") else None
            if cfg and cfg.type == NodeType.AGENT:
                operator_agent = getattr(cfg, "agent", None)
                operators.append({"type": "map", "params": {"agent": operator_agent}})
                if operator_agent is not None:
                    agents.append(operator_agent)
            else:
                operators.append(
                    {
                        "type": "map",
                        "params": {"function": getattr(cfg, "function", None)},
                    }
                )
        return StreamConfig(
            name=f"{route_config.name}_stream",
            type=StreamType.COLD,
            operators=operators,
            agents=agents,
        )

    def _get_message_count(self, route_name: str) -> int:
        record = self._active_conversions.get(route_name)
        if not record:
            return 0
        instance = record.get("instance")
        # LangGraph holds update_count on state_manager
        if hasattr(instance, "state_manager") and hasattr(
            instance.state_manager, "update_count"
        ):
            try:
                return int(instance.state_manager.update_count)
            except Exception:
                return 0
        # Fallback if instance itself exposes update_count
        if hasattr(instance, "update_count"):
            try:
                return int(instance.update_count)
            except Exception:
                return 0
        return 0
