"""Unified route models and complexity analysis (actor-first).

Ported from v2 reactive.route with actor-first semantics. Stream/graph configs
are owned by this module; bridge config supports upgrading/downgrading between
stream and graph routes. Provider/model flags are not used—actors drive routing.
"""

from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import Any

from pydantic import BaseModel, Field

from cleveragents.core.exceptions import ConfigurationError
from cleveragents.langgraph.graph import GraphConfig
from cleveragents.langgraph.nodes import Edge, NodeConfig, NodeType
from cleveragents.reactive.stream_router import StreamConfig, StreamType


class RouteType(Enum):
    """Types of routes available in the system."""

    STREAM = "stream"
    GRAPH = "graph"
    BRIDGE = "bridge"

    def __str__(self) -> str:  # pragma: no cover - convenience
        return self.value


class BridgeConfig(BaseModel):
    """Configuration for type conversion/bridging between routes."""

    upgrade_conditions: dict[str, Any] = Field(default_factory=dict)
    downgrade_conditions: dict[str, Any] = Field(default_factory=dict)
    state_extractor: str | None = None
    state_flattener: str | None = None
    preserve_subscriptions: bool = True
    preserve_checkpointing: bool = True


class RouteConfig(BaseModel):  # pylint: disable=too-many-instance-attributes
    """Unified configuration for all route types."""

    name: str
    type: RouteType

    # Stream-specific config
    stream_type: StreamType | None = None
    operators: list[dict[str, Any]] = Field(default_factory=list)
    subscriptions: list[str] = Field(default_factory=list)
    publications: list[str] = Field(default_factory=list)
    agents: list[str] = Field(default_factory=list)
    initial_value: Any | None = None
    buffer_size: int = 1

    # Graph-specific config
    nodes: dict[str, dict[str, Any]] = Field(default_factory=dict)
    edges: list[dict[str, Any]] = Field(default_factory=list)
    entry_point: str = "start"
    checkpointing: bool = False
    checkpoint_dir: str | None = None
    enable_time_travel: bool = False
    parallel_execution: bool = True
    state_class: str | None = None

    # Bridge config
    bridge: BridgeConfig | None = None

    # Common metadata
    metadata: dict[str, Any] = Field(default_factory=dict)
    template_config: dict[str, Any] | None = None

    def __init__(self, **data: Any) -> None:
        super().__init__(**data)
        if self.type == RouteType.STREAM:
            if not self.stream_type:
                self.stream_type = StreamType.COLD
        elif self.type == RouteType.GRAPH and not self.nodes:
            raise ConfigurationError(
                f"Route '{self.name}' of type 'graph' must have nodes defined"
            )

    def to_stream_config(self) -> StreamConfig:
        if self.type != RouteType.STREAM:
            raise ValueError(f"Cannot convert {self.type} route to StreamConfig")

        return StreamConfig(
            name=self.name,
            type=self.stream_type or StreamType.COLD,
            operators=self.operators,
            subscriptions=self.subscriptions,
            publications=self.publications,
            agents=self.agents,
            initial_value=self.initial_value,
            buffer_size=self.buffer_size,
            template_config=self.template_config,
        )

    def to_graph_config(self) -> GraphConfig:
        if self.type != RouteType.GRAPH:
            raise ValueError(f"Cannot convert {self.type} route to GraphConfig")

        node_configs = {}
        for node_name, node_data in self.nodes.items():
            node_type_str = node_data.get("type", "agent")
            node_type = NodeType[node_type_str.upper()]

            metadata = node_data.get("metadata", {}).copy()
            if node_type == NodeType.MESSAGE_ROUTER and "rules" in node_data:
                metadata["rules"] = node_data["rules"]

            node_configs[node_name] = NodeConfig(
                name=node_name,
                type=node_type,
                agent=node_data.get("agent"),
                function=node_data.get("function"),
                tools=node_data.get("tools", []),
                retry_policy=node_data.get("retry_policy"),
                timeout=node_data.get("timeout"),
                parallel=node_data.get("parallel", False),
                condition=node_data.get("condition"),
                subgraph=node_data.get("subgraph"),
                metadata=metadata,
            )

        edge_objects = [
            Edge(
                source=edge_data["source"],
                target=edge_data["target"],
                condition=edge_data.get("condition"),
                metadata=edge_data.get("metadata", {}),
            )
            for edge_data in self.edges
        ]

        return GraphConfig(
            name=self.name,
            nodes=node_configs,
            edges=edge_objects,
            entry_point=self.entry_point,
            state_class=None,
            checkpointing=self.checkpointing,
            checkpoint_dir=Path(self.checkpoint_dir) if self.checkpoint_dir else None,
            enable_time_travel=self.enable_time_travel,
            parallel_execution=self.parallel_execution,
            metadata=self.metadata,
        )

    @classmethod
    def from_stream_config(cls, stream_config: StreamConfig) -> RouteConfig:
        return cls(
            name=stream_config.name,
            type=RouteType.STREAM,
            stream_type=stream_config.type,
            operators=stream_config.operators,
            subscriptions=stream_config.subscriptions,
            publications=stream_config.publications,
            agents=stream_config.agents,
            initial_value=stream_config.initial_value,
            buffer_size=stream_config.buffer_size,
            template_config=stream_config.template_config,
        )

    @classmethod
    def from_graph_config(cls, graph_config: GraphConfig) -> RouteConfig:
        nodes_dict: dict[str, dict[str, Any]] = {}
        for name, node_config in graph_config.nodes.items():
            # Accept Node or NodeConfig
            cfg = getattr(node_config, "config", node_config)
            node_type = getattr(cfg, "type", None)
            node_type_val = (
                node_type.value
                if node_type is not None and hasattr(node_type, "value")
                else str(node_type or "function")
            )
            node_dict: dict[str, Any] = {
                "type": node_type_val,
                "parallel": getattr(cfg, "parallel", False),
            }
            if getattr(cfg, "agent", None):
                node_dict["agent"] = cfg.agent
            if getattr(cfg, "function", None):
                node_dict["function"] = cfg.function
            if getattr(cfg, "tools", None):
                node_dict["tools"] = cfg.tools
            if getattr(cfg, "retry_policy", None):
                node_dict["retry_policy"] = cfg.retry_policy
            if getattr(cfg, "timeout", None):
                node_dict["timeout"] = cfg.timeout
            if getattr(cfg, "metadata", None):
                node_dict["metadata"] = cfg.metadata
            nodes_dict[name] = node_dict

        edges_list: list[dict[str, Any]] = []
        for edge in graph_config.edges:
            edge_dict: dict[str, Any] = {
                "source": getattr(edge, "source", None),
                "target": getattr(edge, "target", None),
            }
            if getattr(edge, "condition", None):
                edge_dict["condition"] = edge.condition
            edges_list.append(edge_dict)

        return cls(
            name=graph_config.name,
            type=RouteType.GRAPH,
            nodes=nodes_dict,
            edges=edges_list,
            entry_point=getattr(graph_config, "entry_point", "start"),
            checkpointing=getattr(graph_config, "checkpointing", False),
            checkpoint_dir=(
                str(graph_config.checkpoint_dir)
                if getattr(graph_config, "checkpoint_dir", None)
                else None
            ),
            enable_time_travel=getattr(graph_config, "enable_time_travel", False),
            parallel_execution=getattr(graph_config, "parallel_execution", True),
            state_class=None,
            metadata=getattr(graph_config, "metadata", {}),
        )


class RouteComplexityAnalyzer:
    """Analyzes route complexity to help choose stream vs graph."""

    @staticmethod
    def analyze_route(config: RouteConfig) -> dict[str, Any]:
        if config.type == RouteType.STREAM:
            return RouteComplexityAnalyzer._analyze_stream(config)
        if config.type == RouteType.GRAPH:
            return RouteComplexityAnalyzer._analyze_graph(config)
        return {"complexity": "bridge", "score": 0}

    @staticmethod
    def _analyze_stream(config: RouteConfig) -> dict[str, Any]:
        score = 1
        features: list[str] = []

        if config.operators:
            score += len(config.operators)
            features.append(f"{len(config.operators)} operators")

        if config.subscriptions or config.publications:
            score += 2
            features.append("routing connections")

        if config.stream_type == StreamType.HOT:
            score += 1
            features.append("hot/stateful stream")

        return {
            "type": "stream",
            "complexity": (
                "simple" if score <= 2 else "moderate" if score <= 5 else "complex"
            ),
            "score": score,
            "features": features,
            "recommendation": RouteComplexityAnalyzer._get_stream_recommendation(score),
        }

    @staticmethod
    def _analyze_graph(config: RouteConfig) -> dict[str, Any]:
        score = 5
        features: list[str] = []

        node_count = len(config.nodes)
        edge_count = len(config.edges)

        score += node_count + (edge_count // 2)
        features.append(f"{node_count} nodes, {edge_count} edges")

        if config.checkpointing:
            score += 3
            features.append("checkpointing enabled")

        if config.enable_time_travel:
            score += 2
            features.append("time travel enabled")

        conditional_edges = sum(1 for edge in config.edges if "condition" in edge)
        if conditional_edges:
            score += conditional_edges * 2
            features.append(f"{conditional_edges} conditional edges")

        return {
            "type": "graph",
            "complexity": (
                "moderate" if score <= 10 else "complex" if score <= 15 else "advanced"
            ),
            "score": score,
            "features": features,
            "recommendation": RouteComplexityAnalyzer._get_graph_recommendation(score),
        }

    @staticmethod
    def _get_stream_recommendation(score: int) -> str:
        if score <= 2:
            return "Good for simple transformations and filtering"
        if score <= 5:
            return "Suitable for multi-step processing pipelines"
        return "Consider using a graph if you need conditional logic or state"

    @staticmethod
    def _get_graph_recommendation(score: int) -> str:
        if score <= 10:
            return "Good for workflows with conditional logic"
        if score <= 15:
            return "Suitable for complex stateful workflows"
        return "Advanced setup - ensure you need all features"

    @staticmethod
    def suggest_route_type(requirements: dict[str, Any]) -> RouteType:
        needs_state = requirements.get("needs_state", False)
        needs_conditionals = requirements.get("needs_conditionals", False)
        needs_persistence = requirements.get("needs_persistence", False)
        is_continuous = requirements.get("is_continuous", False)
        is_stateless = requirements.get("is_stateless", True)

        if needs_persistence or (needs_state and needs_conditionals):
            return RouteType.GRAPH
        if needs_conditionals and not is_continuous:
            return RouteType.GRAPH
        if is_stateless and is_continuous:
            return RouteType.STREAM
        return RouteType.STREAM
