# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
# actionExecutor.py
# Action execution functionality for workflows

import logging
from typing import Dict, Any, List
from modules.datamodels.datamodelChat import ActionResult, ActionItem, TaskStep
from modules.datamodels.datamodelChat import ChatWorkflow
from modules.workflows.processing.shared.methodDiscovery import methods
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped

logger = logging.getLogger(__name__)

class ActionExecutor:
    """Handles execution of workflow actions"""
    
    def __init__(self, services):
        self.services = services
    
    
    async def executeAction(self, methodName: str, actionName: str, parameters: Dict[str, Any]) -> ActionResult:
        """Execute a method action"""
        try:
            if methodName not in methods:
                raise ValueError(f"Unknown method: {methodName}")
                
            method = methods[methodName]
            if actionName not in method['actions']:
                raise ValueError(f"Unknown action: {actionName} for method {methodName}")
                
            action = method['actions'][actionName]
            
            # Execute the action
            return await action['method'](parameters)
            
        except Exception as e:
            logger.error(f"Error executing method {methodName}.{actionName}: {str(e)}")
            raise
    
    async def executeCompoundAction(self, compoundActionName: str, parameters: Dict[str, Any]) -> ActionResult:
        """Execute a compound action (method.action format)"""
        try:
            # Parse compound action name (e.g., "ai.process" -> method="ai", action="process")
            if '.' not in compoundActionName:
                raise ValueError(f"Invalid compound action name: {compoundActionName}. Expected format: method.action")
            
            methodName, actionName = compoundActionName.split('.', 1)
            
            # Execute using the existing method
            return await self.executeAction(methodName, actionName, parameters)
            
        except Exception as e:
            logger.error(f"Error executing compound action {compoundActionName}: {str(e)}")
            raise
    
    async def executeSingleAction(self, action: ActionItem, workflow: ChatWorkflow, taskStep: TaskStep) -> ActionResult:
        """Execute a single action and return ActionResult with enhanced document processing"""
        try:
            # Check workflow status before executing action
            checkWorkflowStopped(self.services)
            
            # Get indices from workflow state
            taskIndex = workflow.getTaskIndex()
            actionIndex = workflow.getActionIndex()
            
            taskNum = taskIndex
            actionNum = actionIndex
            
            logger.info(f"=== TASK {taskNum} ACTION {actionNum}: {action.execMethod}.{action.execAction} ===")
            
            # Log input parameters
            inputDocs = action.execParameters.get('documentList', [])
            inputConnections = action.execParameters.get('connections', [])
            logger.info(f"Input documents: {inputDocs} (type: {type(inputDocs)})")
            if inputConnections:
                logger.info(f"Input connections: {inputConnections}")
            
            # Log all action parameters for debugging
            logger.info(f"All action parameters: {action.execParameters}")
            
            enhancedParameters = action.execParameters.copy()
            if action.expectedDocumentFormats:
                enhancedParameters['expectedDocumentFormats'] = action.expectedDocumentFormats
                logger.info(f"Expected formats: {action.expectedDocumentFormats}")
            
            # Get current task execution operationId (taskExec_*) - this is the parent of the action
            taskOperationId = None
            try:
                progressLogger = self.services.chat.createProgressLogger()
                activeOperations = progressLogger.getActiveOperations()
                logger.debug(f"Looking for task operation ID. Active operations: {list(activeOperations.keys())}")
                
                # Look for task execution operation (starts with "taskExec_")
                # This is the Task level that should be parent of this action
                for opId in activeOperations.keys():
                    if opId.startswith("taskExec_"):
                        taskOperationId = opId
                        logger.info(f"Found task operation ID: {taskOperationId} for action {action.execMethod}.{action.execAction}")
                        break
                
                if not taskOperationId:
                    logger.error(f"CRITICAL: No taskExec_ operation found in active operations. Active operations: {list(activeOperations.keys())}. Action logs will appear at root level!")
            except Exception as e:
                logger.error(f"Error getting task operation ID: {str(e)}")
            
            # Create action operationId entry - Action is child of Task
            import time
            actionOperationId = f"action_{action.execMethod}_{action.execAction}_{workflow.id}_{taskNum}_{actionNum}_{int(time.time())}"
            
            try:
                # Start action progress tracking - Action is child of Task
                # CRITICAL: If taskOperationId is None, the action will appear at root level
                self.services.chat.progressLogStart(
                    actionOperationId,
                    action.execMethod.capitalize(),
                    action.execAction,
                    f"Task {taskNum} Action {actionNum}",
                    parentOperationId=taskOperationId  # Will be None if taskExec_ not found
                )
            except Exception as e:
                logger.error(f"Error starting action progress log: {str(e)}")
            
            # Add action operationId to parameters so action methods can use it for their steps
            # Action steps should be children of the action, not the task
            # CRITICAL: This must always be set, even if taskOperationId is None
            enhancedParameters['parentOperationId'] = actionOperationId
            if taskOperationId:
                logger.info(f"Created action operationId '{actionOperationId}' (parent: {taskOperationId}) for action {action.execMethod}.{action.execAction}")
            else:
                logger.warning(f"Created action operationId '{actionOperationId}' WITHOUT parent (taskExec_ not found) for action {action.execMethod}.{action.execAction}. Action will appear at root level!")
            
            # Check workflow status before executing the action
            checkWorkflowStopped(self.services)
            
            # Execute action and track success for progress log
            result = None
            actionSuccess = False
            try:
                result = await self.executeAction(
                    methodName=action.execMethod,
                    actionName=action.execAction,
                    parameters=enhancedParameters
                )
                actionSuccess = result.success if result else False
            except Exception as e:
                logger.error(f"Error executing action: {str(e)}")
                actionSuccess = False
            finally:
                # Finish action progress tracking
                try:
                    self.services.chat.progressLogFinish(actionOperationId, actionSuccess)
                except Exception as e:
                    logger.error(f"Error finishing action progress log: {str(e)}")
            
            # If action execution failed, return error result
            if result is None:
                action.setError("Action execution failed")
                return ActionResult(
                    success=False,
                    documents=[],
                    resultLabel=action.execResultLabel,
                    error="Action execution failed"
                )
            
            resultLabel = action.execResultLabel
            
            # Trace action result with full document metadata
            actionResultTrace = {
                "method": action.execMethod,
                "action": action.execAction,
                "success": result.success,
                "error": result.error,
                "resultLabel": resultLabel,
                "documentsCount": len(result.documents) if result.documents else 0
            }
            
            # Add full document metadata if documents exist
            if result.documents:
                actionResultTrace["documents"] = []
                for doc in result.documents:
                    docMetadata = {
                        "name": getattr(doc, 'documentName', 'Unknown'),
                        "mimeType": getattr(doc, 'mimeType', 'Unknown'),
                        "size": getattr(doc, 'size', 'Unknown'),
                        "created": getattr(doc, 'created', 'Unknown'),
                        "modified": getattr(doc, 'modified', 'Unknown'),
                        "typeGroup": getattr(doc, 'typeGroup', 'Unknown'),
                        "documentId": getattr(doc, 'documentId', 'Unknown'),
                        "reference": getattr(doc, 'reference', 'Unknown')
                    }
                    # Remove 'Unknown' values to keep it clean
                    docMetadata = {k: v for k, v in docMetadata.items() if v != 'Unknown'}
                    actionResultTrace["documents"].append(docMetadata)
            
            
            # Process action result
            if result.success:
                action.setSuccess()
                # Extract result text from ALL documents using generation service
                action.result = self._extractResultText(result)
                # Preserve the action's execResultLabel for document routing
                # Action methods should NOT return resultLabel - this is managed by the action handler
                if not action.execResultLabel:
                    logger.warning(f"Action {action.execMethod}.{action.execAction} has no execResultLabel set")
                
                # Log action results
                logger.info(f"Action completed successfully")
                
                if result.documents:
                    logger.info(f"Output documents ({len(result.documents)}):")
                    for i, doc in enumerate(result.documents):
                        logger.info(f"  {i+1}. {doc.documentName}")
                else:
                    logger.info("Output: No documents created")
            else:
                action.setError(result.error or "Action execution failed")
                logger.error(f"Action failed: {result.error}")
                
                # Create database log entry for action failure (write-through + bind)
                self.services.chat.storeLog(workflow, {
                    "message": f"❌ **Task {taskNum}**❌ **Action {actionNum}** failed: {result.error}",
                    "type": "error",
                    "progress": 1.0
                })

            # Log action summary
            logger.info(f"=== TASK {taskNum} ACTION {actionNum} COMPLETED ===")
            
            # Increment action index in workflow
            workflow.incrementAction()
            
            # Create action completion message with documents (generic)
            await self._createActionCompletionMessage(action, result, workflow, taskStep, taskIndex, actionIndex)
            
            return ActionResult(
                success=result.success,
                documents=result.documents,  # Return original ActionDocument objects
                resultLabel=action.execResultLabel,  # Always use action's execResultLabel
                error=result.error or ""
            )
        except Exception as e:
            logger.error(f"Error executing single action: {str(e)}")
            action.setError(str(e))
            return ActionResult(
                success=False,
                documents=[],  # Empty documents for error case
                resultLabel=action.execResultLabel,
                error=str(e)
            )
    
    def _extractResultText(self, result: ActionResult) -> str:
        """Extract result text from ActionResult documents"""
        if not result.success or not result.documents:
            return ""
        
        # Extract text directly from ActionDocument objects
        resultParts = []
        for doc in result.documents:
            if hasattr(doc, 'documentData') and doc.documentData:
                resultParts.append(str(doc.documentData))
        
        # Join all document results with separators
        return "\n\n---\n\n".join(resultParts) if resultParts else ""
    
    async def _createActionCompletionMessage(self, action: ActionItem, result: ActionResult, workflow: ChatWorkflow, 
                                          taskStep: TaskStep, taskIndex: int, actionIndex: int):
        """Create action completion message with documents (generic)"""
        try:
            # Convert ActionDocument objects to ChatDocument objects for message creation
            createdDocuments = []
            if result.documents:
                createdDocuments = self.services.generation.createDocumentsFromActionResult(result, action, workflow, None)
            
            # Create action message using message creator
            from modules.workflows.processing.core.messageCreator import MessageCreator
            messageCreator = MessageCreator(self.services)
            
            await messageCreator.createActionMessage(
                action=action,
                result=result,
                workflow=workflow,
                resultLabel=action.execResultLabel,
                createdDocuments=createdDocuments,
                taskStep=taskStep,
                taskIndex=taskIndex,
                actionIndex=actionIndex,
                totalActions=None  # Not needed - removed from signature
            )
        except Exception as e:
            logger.error(f"Error creating action completion message: {str(e)}")
    
