# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Workflow Toolbox - AI-assisted graph manipulation tools for the GraphicalEditor.
Tools: readWorkflowGraph, addNode, removeNode, connectNodes, setNodeParameter,
       listAvailableNodeTypes, validateGraph, listWorkflowHistory, readWorkflowMessages.
"""

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

from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolResult

logger = logging.getLogger(__name__)

TOOLBOX_ID = "workflow"


async def _readWorkflowGraph(params: Dict[str, Any], context: Any) -> ToolResult:
    """Read the current workflow graph (nodes and connections)."""
    try:
        workflowId = params.get("workflowId")
        instanceId = params.get("instanceId")
        if not workflowId or not instanceId:
            return ToolResult(success=False, error="workflowId and instanceId required")

        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        wf = iface.getWorkflow(workflowId)
        if not wf:
            return ToolResult(success=False, error=f"Workflow {workflowId} not found")

        graph = wf.get("graph", {})
        nodes = graph.get("nodes", [])
        connections = graph.get("connections", [])
        return ToolResult(
            success=True,
            data={
                "workflowId": workflowId,
                "label": wf.get("label", ""),
                "nodeCount": len(nodes),
                "connectionCount": len(connections),
                "nodes": [{"id": n.get("id"), "type": n.get("type"), "title": n.get("title", "")} for n in nodes],
                "connections": connections,
            },
        )
    except Exception as e:
        logger.exception("readWorkflowGraph failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _addNode(params: Dict[str, Any], context: Any) -> ToolResult:
    """Add a node to the workflow graph."""
    try:
        workflowId = params.get("workflowId")
        instanceId = params.get("instanceId")
        nodeType = params.get("nodeType")
        if not workflowId or not instanceId or not nodeType:
            return ToolResult(success=False, error="workflowId, instanceId, and nodeType required")

        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        wf = iface.getWorkflow(workflowId)
        if not wf:
            return ToolResult(success=False, error=f"Workflow {workflowId} not found")

        graph = dict(wf.get("graph", {}))
        nodes = list(graph.get("nodes", []))

        nodeId = params.get("nodeId") or str(uuid.uuid4())[:8]
        title = params.get("title", "")
        nodeParams = params.get("parameters", {})
        position = params.get("position", {"x": len(nodes) * 200, "y": 100})

        newNode = {
            "id": nodeId,
            "type": nodeType,
            "title": title,
            "parameters": nodeParams,
            "position": position,
        }
        nodes.append(newNode)
        graph["nodes"] = nodes

        iface.updateWorkflow(workflowId, {"graph": graph})
        return ToolResult(
            success=True,
            data={"nodeId": nodeId, "nodeType": nodeType, "message": f"Node '{title or nodeType}' added"},
        )
    except Exception as e:
        logger.exception("addNode failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _removeNode(params: Dict[str, Any], context: Any) -> ToolResult:
    """Remove a node and its connections from the workflow graph."""
    try:
        workflowId = params.get("workflowId")
        instanceId = params.get("instanceId")
        nodeId = params.get("nodeId")
        if not workflowId or not instanceId or not nodeId:
            return ToolResult(success=False, error="workflowId, instanceId, and nodeId required")

        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        wf = iface.getWorkflow(workflowId)
        if not wf:
            return ToolResult(success=False, error=f"Workflow {workflowId} not found")

        graph = dict(wf.get("graph", {}))
        nodes = [n for n in graph.get("nodes", []) if n.get("id") != nodeId]
        connections = [
            c for c in graph.get("connections", [])
            if c.get("source") != nodeId and c.get("target") != nodeId
        ]
        graph["nodes"] = nodes
        graph["connections"] = connections

        iface.updateWorkflow(workflowId, {"graph": graph})
        return ToolResult(success=True, data={"nodeId": nodeId, "message": f"Node {nodeId} removed"})
    except Exception as e:
        logger.exception("removeNode failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _connectNodes(params: Dict[str, Any], context: Any) -> ToolResult:
    """Connect two nodes in the workflow graph."""
    try:
        workflowId = params.get("workflowId")
        instanceId = params.get("instanceId")
        sourceId = params.get("sourceId")
        targetId = params.get("targetId")
        if not workflowId or not instanceId or not sourceId or not targetId:
            return ToolResult(success=False, error="workflowId, instanceId, sourceId, and targetId required")

        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        wf = iface.getWorkflow(workflowId)
        if not wf:
            return ToolResult(success=False, error=f"Workflow {workflowId} not found")

        graph = dict(wf.get("graph", {}))
        connections = list(graph.get("connections", []))
        newConn = {
            "source": sourceId,
            "target": targetId,
            "sourceOutput": params.get("sourceOutput", 0),
            "targetInput": params.get("targetInput", 0),
        }
        connections.append(newConn)
        graph["connections"] = connections

        iface.updateWorkflow(workflowId, {"graph": graph})
        return ToolResult(success=True, data={"connection": newConn, "message": f"Connected {sourceId} -> {targetId}"})
    except Exception as e:
        logger.exception("connectNodes failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _setNodeParameter(params: Dict[str, Any], context: Any) -> ToolResult:
    """Set a parameter on a node."""
    try:
        workflowId = params.get("workflowId")
        instanceId = params.get("instanceId")
        nodeId = params.get("nodeId")
        paramName = params.get("parameterName")
        paramValue = params.get("parameterValue")
        if not workflowId or not instanceId or not nodeId or not paramName:
            return ToolResult(success=False, error="workflowId, instanceId, nodeId, and parameterName required")

        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        wf = iface.getWorkflow(workflowId)
        if not wf:
            return ToolResult(success=False, error=f"Workflow {workflowId} not found")

        graph = dict(wf.get("graph", {}))
        nodes = list(graph.get("nodes", []))
        found = False
        for n in nodes:
            if n.get("id") == nodeId:
                nodeParams = dict(n.get("parameters", {}))
                nodeParams[paramName] = paramValue
                n["parameters"] = nodeParams
                found = True
                break

        if not found:
            return ToolResult(success=False, error=f"Node {nodeId} not found in graph")

        graph["nodes"] = nodes
        iface.updateWorkflow(workflowId, {"graph": graph})
        return ToolResult(success=True, data={"nodeId": nodeId, "parameter": paramName, "message": f"Parameter '{paramName}' set"})
    except Exception as e:
        logger.exception("setNodeParameter failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _listAvailableNodeTypes(params: Dict[str, Any], context: Any) -> ToolResult:
    """List all available node types for the flow builder."""
    try:
        from modules.features.graphicalEditor.nodeDefinitions import STATIC_NODE_TYPES
        nodeTypes = [
            {"id": n.get("id"), "category": n.get("category"), "label": n.get("label", {}).get("en", n.get("id"))}
            for n in STATIC_NODE_TYPES
        ]
        return ToolResult(success=True, data={"nodeTypes": nodeTypes, "count": len(nodeTypes)})
    except Exception as e:
        logger.exception("listAvailableNodeTypes failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _validateGraph(params: Dict[str, Any], context: Any) -> ToolResult:
    """Validate a workflow graph for common issues."""
    try:
        workflowId = params.get("workflowId")
        instanceId = params.get("instanceId")
        if not workflowId or not instanceId:
            return ToolResult(success=False, error="workflowId and instanceId required")

        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        wf = iface.getWorkflow(workflowId)
        if not wf:
            return ToolResult(success=False, error=f"Workflow {workflowId} not found")

        graph = wf.get("graph", {})
        nodes = graph.get("nodes", [])
        connections = graph.get("connections", [])
        issues: List[str] = []

        nodeIds = {n.get("id") for n in nodes}
        if not nodes:
            issues.append("Graph has no nodes")

        hasTrigger = any(n.get("type", "").startswith("trigger.") for n in nodes)
        if not hasTrigger:
            issues.append("No trigger node found")

        for c in connections:
            if c.get("source") not in nodeIds:
                issues.append(f"Connection source '{c.get('source')}' not found")
            if c.get("target") not in nodeIds:
                issues.append(f"Connection target '{c.get('target')}' not found")

        connectedNodes = set()
        for c in connections:
            connectedNodes.add(c.get("source"))
            connectedNodes.add(c.get("target"))
        orphans = [n.get("id") for n in nodes if n.get("id") not in connectedNodes and not n.get("type", "").startswith("trigger.")]
        if orphans:
            issues.append(f"Orphan nodes (not connected): {', '.join(orphans)}")

        return ToolResult(
            success=True,
            data={
                "valid": len(issues) == 0,
                "issues": issues,
                "nodeCount": len(nodes),
                "connectionCount": len(connections),
            },
        )
    except Exception as e:
        logger.exception("validateGraph failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _listWorkflowHistory(params: Dict[str, Any], context: Any) -> ToolResult:
    """List versions (history) for a workflow."""
    try:
        workflowId = params.get("workflowId", "")
        instanceId = params.get("instanceId", "")
        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        versions = iface.getVersions(workflowId)
        return ToolResult(
            success=True,
            data={
                "workflowId": workflowId,
                "versions": [
                    {
                        "id": v.get("id"),
                        "versionNumber": v.get("versionNumber"),
                        "status": v.get("status"),
                        "publishedAt": v.get("publishedAt"),
                        "publishedBy": v.get("publishedBy"),
                    }
                    for v in versions
                ],
            },
        )
    except Exception as e:
        logger.exception("listWorkflowHistory failed: %s", e)
        return ToolResult(success=False, error=str(e))


async def _readWorkflowMessages(params: Dict[str, Any], context: Any) -> ToolResult:
    """Read recent run logs/messages for a workflow."""
    try:
        workflowId = params.get("workflowId", "")
        instanceId = params.get("instanceId", "")
        from modules.features.graphicalEditor.interfaceFeatureGraphicalEditor import getGraphicalEditorInterface
        user = getattr(context, "user", None)
        mandateId = getattr(context, "mandateId", "") or ""
        iface = getGraphicalEditorInterface(user, mandateId, instanceId)
        from modules.features.graphicalEditor.datamodelFeatureGraphicalEditor import AutoRun
        runs = iface.db.getRecordset(AutoRun, recordFilter={"workflowId": workflowId}) or []
        runSummaries = []
        for r in sorted(runs, key=lambda x: x.get("startedAt") or 0, reverse=True)[:10]:
            runSummaries.append({
                "runId": r.get("id"),
                "status": r.get("status"),
                "startedAt": r.get("startedAt"),
                "completedAt": r.get("completedAt"),
                "error": r.get("error"),
            })
        return ToolResult(
            success=True,
            data={"workflowId": workflowId, "recentRuns": runSummaries},
        )
    except Exception as e:
        logger.exception("readWorkflowMessages failed: %s", e)
        return ToolResult(success=False, error=str(e))


def getWorkflowToolDefinitions() -> List[Dict[str, Any]]:
    """Return tool definitions for registration in the ToolRegistry."""
    return [
        {
            "name": "readWorkflowGraph",
            "handler": _readWorkflowGraph,
            "description": "Read the current workflow graph (nodes and connections)",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string", "description": "Workflow ID"},
                    "instanceId": {"type": "string", "description": "Feature instance ID"},
                },
                "required": ["workflowId", "instanceId"],
            },
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "addNode",
            "handler": _addNode,
            "description": "Add a node to the workflow graph",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                    "nodeType": {"type": "string", "description": "Node type (e.g. ai.chat, email.send)"},
                    "title": {"type": "string", "description": "Human-readable title"},
                    "parameters": {"type": "object", "description": "Node parameters"},
                    "position": {"type": "object", "description": "Canvas position {x, y}"},
                },
                "required": ["workflowId", "instanceId", "nodeType"],
            },
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "removeNode",
            "handler": _removeNode,
            "description": "Remove a node and its connections from the graph",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                    "nodeId": {"type": "string", "description": "ID of the node to remove"},
                },
                "required": ["workflowId", "instanceId", "nodeId"],
            },
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "connectNodes",
            "handler": _connectNodes,
            "description": "Connect two nodes in the graph",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                    "sourceId": {"type": "string"},
                    "targetId": {"type": "string"},
                    "sourceOutput": {"type": "integer", "default": 0},
                    "targetInput": {"type": "integer", "default": 0},
                },
                "required": ["workflowId", "instanceId", "sourceId", "targetId"],
            },
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "setNodeParameter",
            "handler": _setNodeParameter,
            "description": "Set a parameter on a node",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                    "nodeId": {"type": "string"},
                    "parameterName": {"type": "string"},
                    "parameterValue": {"description": "Value to set (any type)"},
                },
                "required": ["workflowId", "instanceId", "nodeId", "parameterName", "parameterValue"],
            },
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "listAvailableNodeTypes",
            "handler": _listAvailableNodeTypes,
            "description": "List all available node types for the flow builder",
            "parameters": {"type": "object", "properties": {}},
            "readOnly": True,
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "validateGraph",
            "handler": _validateGraph,
            "description": "Validate a workflow graph for common issues",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                },
                "required": ["workflowId", "instanceId"],
            },
            "readOnly": True,
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "listWorkflowHistory",
            "handler": _listWorkflowHistory,
            "description": "List version history for a workflow (AutoVersion entries)",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                },
                "required": ["workflowId", "instanceId"],
            },
            "readOnly": True,
            "toolSet": TOOLBOX_ID,
        },
        {
            "name": "readWorkflowMessages",
            "handler": _readWorkflowMessages,
            "description": "Read recent run logs and status for a workflow",
            "parameters": {
                "type": "object",
                "properties": {
                    "workflowId": {"type": "string"},
                    "instanceId": {"type": "string"},
                },
                "required": ["workflowId", "instanceId"],
            },
            "readOnly": True,
            "toolSet": TOOLBOX_ID,
        },
    ]
