"""Pathway Editor — node/connection mutations.

Handles add/update/delete for nodes and connections in the registry.
"""

import logging
from typing import Any

from core.common import JsonObject
from core.pathways.loader import node_from_dict
from core.pathways.pathway import Connection, Node, Pathway, PathwayMode
from core.pathways.registry import PathwayMeta, PathwayRegistry
from core.pathways.validation import find_cycle

logger = logging.getLogger(__name__)


class PathwayEditor:
    """Mutates pathway nodes and connections."""

    def __init__(
        self,
        *,
        registry: PathwayRegistry,
    ) -> None:
        self._registry = registry

    def add_node(
        self,
        pathway_id: str,
        node_data: JsonObject,
    ) -> tuple[Pathway, Node]:
        """Add a node to a pathway.

        Returns
        -------
            Tuple of (updated Pathway, new node)
        """
        pathway = self._registry.get(pathway_id)
        if pathway is None:
            msg = f"Pathway not found: {pathway_id}"
            raise ValueError(msg)

        node = node_from_dict(node_data)

        if node.id in pathway.nodes:
            msg = f"Node ID already exists: {node.id}"
            raise ValueError(msg)

        pathway.nodes[node.id] = node
        self._registry.put(pathway_id, pathway)

        meta = self._registry.get_meta(pathway_id)
        if meta:
            meta.node_count = len(pathway.nodes)

        logger.debug("Added node %s to pathway %s", node.id, pathway_id)
        return pathway, node

    def update_node(
        self,
        pathway_id: str,
        node_id: str,
        updates: JsonObject,
    ) -> tuple[Pathway, Node]:
        """Update a node's configuration.

        Returns
        -------
            Tuple of (updated Pathway, updated node)
        """
        pathway = self._registry.get(pathway_id)
        if pathway is None:
            msg = f"Pathway not found: {pathway_id}"
            raise ValueError(msg)

        if node_id not in pathway.nodes:
            msg = f"Node not found: {node_id}"
            raise ValueError(msg)

        old_node = pathway.nodes[node_id]

        # Build update dict from top-level fields and config sub-dict
        update_fields: dict[str, Any] = {}
        if "name" in updates:
            update_fields["name"] = updates["name"]
        if "description" in updates:
            update_fields["description"] = updates["description"]

        # Merge config sub-dict into top-level fields
        if "config" in updates and isinstance(updates["config"], dict):
            update_fields.update(updates["config"])

        new_node = old_node.model_copy(update=update_fields)
        pathway.nodes[node_id] = new_node
        self._registry.put(pathway_id, pathway)

        logger.debug("Updated node %s in pathway %s", node_id, pathway_id)
        return pathway, new_node

    def delete_node(
        self,
        pathway_id: str,
        node_id: str,
        *,
        remove_connections: bool = True,
    ) -> Pathway:
        """Delete a node from a pathway."""
        pathway = self._registry.get(pathway_id)
        if pathway is None:
            msg = f"Pathway not found: {pathway_id}"
            raise ValueError(msg)

        if node_id not in pathway.nodes:
            msg = f"Node not found: {node_id}"
            raise ValueError(msg)

        del pathway.nodes[node_id]

        if remove_connections:
            pathway.connections = [
                conn
                for conn in pathway.connections
                if node_id not in (conn.from_node, conn.to_node)
            ]

        self._registry.put(pathway_id, pathway)

        meta = self._registry.get_meta(pathway_id)
        if meta:
            meta.node_count = len(pathway.nodes)

        logger.debug("Deleted node %s from pathway %s", node_id, pathway_id)
        return pathway

    def add_connection(
        self,
        pathway_id: str,
        conn_data: JsonObject,
    ) -> tuple[Pathway, Connection]:
        """Add a connection between nodes.

        Returns
        -------
            Tuple of (updated Pathway, new Connection)
        """
        pathway = self._registry.get(pathway_id)
        if pathway is None:
            raise ValueError(f"Pathway not found: {pathway_id}")

        from_node = conn_data.get("from_node") or conn_data.get("from", "")
        to_node = conn_data.get("to_node") or conn_data.get("to", "")

        if not from_node or not to_node:
            raise ValueError("Connection requires from_node and to_node")

        if from_node not in pathway.nodes:
            raise ValueError(f"Source node not found: {from_node}")
        if to_node not in pathway.nodes:
            raise ValueError(f"Target node not found: {to_node}")

        if from_node == to_node:
            raise ValueError("Cannot connect a node to itself")

        for existing in pathway.connections:
            if existing.from_node == from_node and existing.to_node == to_node:
                raise ValueError(f"Connection already exists: {from_node} -> {to_node}")

        connection = Connection(
            from_node=from_node,
            to_node=to_node,
            from_output=conn_data.get("from_output", "output"),
            to_input=conn_data.get("to_input", "input"),
        )

        pathway.connections.append(connection)

        if pathway.mode != PathwayMode.STATEFUL:
            try:
                cycle = find_cycle(pathway)
            except Exception:
                cycle = None
            if cycle:
                pathway.connections.pop()
                msg = f"Connection would create a cycle: {' -> '.join(cycle)}"
                raise ValueError(msg)

        self._registry.put(pathway_id, pathway)

        logger.debug("Added connection %s -> %s in pathway %s", from_node, to_node, pathway_id)
        return pathway, connection

    def delete_connection(
        self,
        pathway_id: str,
        from_node: str,
        to_node: str,
    ) -> Pathway:
        """Delete a connection between nodes."""
        pathway = self._registry.get(pathway_id)
        if pathway is None:
            msg = f"Pathway not found: {pathway_id}"
            raise ValueError(msg)

        original_count = len(pathway.connections)
        pathway.connections = [
            conn
            for conn in pathway.connections
            if not (conn.from_node == from_node and conn.to_node == to_node)
        ]

        if len(pathway.connections) == original_count:
            msg = f"Connection not found: {from_node} -> {to_node}"
            raise ValueError(msg)

        self._registry.put(pathway_id, pathway)

        logger.debug("Deleted connection %s -> %s in pathway %s", from_node, to_node, pathway_id)
        return pathway

    def update_metadata(
        self,
        pathway_id: str,
        *,
        name: str | None = None,
        description: str | None = None,
        metadata: JsonObject | None = None,
    ) -> PathwayMeta:
        """Update pathway metadata."""
        pathway = self._registry.get(pathway_id)
        if pathway is None:
            msg = f"Pathway not found: {pathway_id}"
            raise ValueError(msg)

        if name is not None:
            pathway.name = name
        if description is not None:
            pathway.description = description
        if metadata is not None:
            pathway.metadata.update(metadata)

        self._registry.put(pathway_id, pathway)

        meta = self._registry.get_meta(pathway_id)
        if meta:
            if name is not None:
                meta.name = name
            if description is not None:
                meta.description = description
        else:
            meta = PathwayMeta(
                id=pathway_id,
                name=pathway.name,
                description=pathway.description,
                source="user:updated",
                version=None,
                doc_id=None,
                node_count=len(pathway.nodes),
            )
            self._registry.set_meta(pathway_id, meta)

        logger.debug("Updated pathway metadata: %s", pathway_id)
        return meta


__all__ = ["PathwayEditor"]
