"""
/mnt/e/genesis-system/core/automation/workflow_engine.py

Workflow Engine for orchestrating multi-step automation workflows.
"""

import logging
import time
from typing import List, Callable, Dict, Any, Optional, Union
import uuid
import threading
import queue

# Configure logging (following Genesis standard)
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)


class WorkflowStepResult:
    """
    Represents the result of a single workflow step.
    """

    def __init__(self, success: bool, data: Any = None, error: Optional[str] = None):
        self.success = success
        self.data = data
        self.error = error

    def __repr__(self):
        return (
            f"WorkflowStepResult(success={self.success}, data={self.data}, "
            f"error={self.error})"
        )


class Workflow:
    """
    Represents a workflow, composed of multiple steps.
    """

    def __init__(
        self,
        name: str,
        steps: List[Callable[[Dict[str, Any]], WorkflowStepResult]],
        workflow_id: Optional[str] = None,
    ):
        """
        Initializes a new Workflow.

        Args:
            name: The name of the workflow.
            steps: A list of callable workflow steps.  Each step should accept
                   a dictionary of context data and return a WorkflowStepResult.
            workflow_id: An optional workflow ID. If not provided, one will be generated.
        """
        self.name = name
        self.steps = steps
        self.workflow_id = workflow_id or str(uuid.uuid4())
        self.context: Dict[str, Any] = {}  # Context data shared between steps
        self.results: List[WorkflowStepResult] = []
        self._status: str = "Not Started"  # Workflow Status
        self._exception: Optional[Exception] = None

    def execute(self) -> bool:
        """
        Executes the workflow step by step.

        Returns:
            True if the workflow completed successfully, False otherwise.
        """
        self._status = "Running"
        logger.info(f"Workflow '{self.name}' (ID: {self.workflow_id}) started.")

        try:
            for i, step in enumerate(self.steps):
                logger.info(
                    f"Workflow '{self.name}' (ID: {self.workflow_id}), Step {i+1}/{len(self.steps)}: {step.__name__}"
                )
                try:
                    result = step(self.context)
                    self.results.append(result)

                    if not result.success:
                        logger.error(
                            f"Workflow '{self.name}' (ID: {self.workflow_id}), Step {i+1} failed: {result.error}"
                        )
                        self._status = "Failed"
                        return False

                    # Update context with data from the step result
                    if result.data:
                        self.context.update(result.data)

                except Exception as e:
                    logger.exception(
                        f"Workflow '{self.name}' (ID: {self.workflow_id}), Step {i+1} encountered an unexpected error: {e}"
                    )
                    self._status = "Failed"
                    self._exception = e
                    return False

            self._status = "Completed"
            logger.info(f"Workflow '{self.name}' (ID: {self.workflow_id}) completed successfully.")
            return True

        finally:
            if self._status != "Completed":
                logger.error(
                    f"Workflow '{self.name}' (ID: {self.workflow_id}) finished with status: {self._status}"
                )


    def get_status(self) -> str:
        """
        Returns the current status of the workflow.
        """
        return self._status

    def get_results(self) -> List[WorkflowStepResult]:
        """
        Returns a list of WorkflowStepResult objects for each step.
        """
        return self.results

    def get_context(self) -> Dict[str, Any]:
        """
        Returns the current workflow context.
        """
        return self.context

    def get_exception(self) -> Optional[Exception]:
        """
        Returns the exception if workflow execution resulted into an exception.
        """
        return self._exception

    def reset(self):
        """
        Resets the workflow to its initial state, clearing context and results.
        Useful for re-running the same workflow.
        """
        self.context = {}
        self.results = []
        self._status = "Not Started"
        self._exception = None
        logger.info(f"Workflow '{self.name}' (ID: {self.workflow_id}) reset.")


class WorkflowEngine:
    """
    Engine for managing and executing workflows.
    Supports asynchronous execution via threading.
    """

    def __init__(self):
        self.workflows: Dict[str, Workflow] = {}
        self.workflow_threads: Dict[str, threading.Thread] = {}
        self.workflow_queue: queue.Queue = queue.Queue() # Queue for asynchronous workflow execution

    def register_workflow(self, workflow: Workflow):
        """
        Registers a workflow with the engine.

        Args:
            workflow: The Workflow object to register.
        """
        if workflow.workflow_id in self.workflows:
            raise ValueError(
                f"Workflow with ID '{workflow.workflow_id}' already registered."
            )
        self.workflows[workflow.workflow_id] = workflow
        logger.info(
            f"Workflow '{workflow.name}' (ID: {workflow.workflow_id}) registered."
        )

    def get_workflow(self, workflow_id: str) -> Optional[Workflow]:
        """
        Retrieves a workflow by its ID.

        Args:
            workflow_id: The ID of the workflow to retrieve.

        Returns:
            The Workflow object, or None if not found.
        """
        return self.workflows.get(workflow_id)

    def execute_workflow(self, workflow_id: str) -> bool:
        """
        Executes a registered workflow synchronously.

        Args:
            workflow_id: The ID of the workflow to execute.

        Returns:
            True if the workflow completed successfully, False otherwise.
        """
        workflow = self.get_workflow(workflow_id)
        if not workflow:
            logger.error(f"Workflow with ID '{workflow_id}' not found.")
            return False

        return workflow.execute()

    def execute_workflow_async(self, workflow_id: str):
        """
        Executes a registered workflow asynchronously in a separate thread.

        Args:
            workflow_id: The ID of the workflow to execute.
        """
        workflow = self.get_workflow(workflow_id)
        if not workflow:
            logger.error(f"Workflow with ID '{workflow_id}' not found.")
            return

        if workflow_id in self.workflow_threads and self.workflow_threads[workflow_id].is_alive():
            logger.warning(f"Workflow '{workflow.name}' (ID: {workflow_id}) is already running asynchronously.")
            return

        def run_workflow(workflow_id: str):
            workflow = self.get_workflow(workflow_id)
            if workflow:
                workflow.execute()
            else:
                 logger.error(f"Workflow with ID '{workflow_id}' not found during async execution.")


        thread = threading.Thread(target=run_workflow, args=(workflow_id,))
        self.workflow_threads[workflow_id] = thread
        thread.start()
        logger.info(f"Workflow '{workflow.name}' (ID: {workflow_id}) started asynchronously.")

    def get_workflow_status(self, workflow_id: str) -> Optional[str]:
        """
        Retrieves the status of a workflow.

        Args:
            workflow_id: The ID of the workflow.

        Returns:
            The status string or None if the workflow doesn't exist.
        """
        workflow = self.get_workflow(workflow_id)
        if workflow:
            return workflow.get_status()
        return None

    def reset_workflow(self, workflow_id: str):
        """
        Resets a workflow to its initial state.

        Args:
            workflow_id: The ID of the workflow to reset.
        """
        workflow = self.get_workflow(workflow_id)
        if workflow:
            workflow.reset()
        else:
            logger.error(f"Workflow with ID '{workflow_id}' not found.")

if __name__ == "__main__":
    # Example Usage
    def step_1(context: Dict[str, Any]) -> WorkflowStepResult:
        logger.info("Executing Step 1")
        context["step_1_data"] = "Data from step 1"
        return WorkflowStepResult(success=True, data={"new_key": "new_value"})

    def step_2(context: Dict[str, Any]) -> WorkflowStepResult:
        logger.info("Executing Step 2")
        if context.get("step_1_data"):
            logger.info(f"Step 1 Data: {context['step_1_data']}")
        else:
            return WorkflowStepResult(success=False, error="Step 1 data missing")
        return WorkflowStepResult(success=True, data={"step_2_data": "Data from step 2"})

    def step_3(context: Dict[str, Any]) -> WorkflowStepResult:
        logger.info("Executing Step 3")
        if context.get("step_2_data"):
            logger.info(f"Step 2 Data: {context['step_2_data']}")
        else:
            return WorkflowStepResult(success=False, error="Step 2 data missing")
        return WorkflowStepResult(success=True)


    workflow = Workflow(name="Example Workflow", steps=[step_1, step_2, step_3])
    engine = WorkflowEngine()
    engine.register_workflow(workflow)

    # Synchronous Execution
    logger.info("Executing workflow synchronously:")
    success = engine.execute_workflow(workflow.workflow_id)
    logger.info(f"Workflow completed with success: {success}")
    logger.info(f"Workflow Results: {workflow.get_results()}")
    logger.info(f"Workflow Context: {workflow.get_context()}")

    engine.reset_workflow(workflow.workflow_id)

    # Asynchronous Execution
    logger.info("Executing workflow asynchronously:")
    engine.execute_workflow_async(workflow.workflow_id)
    time.sleep(1) # Wait for the async thread to complete
    logger.info(f"Workflow status (async): {engine.get_workflow_status(workflow.workflow_id)}")

    if workflow.get_status() != "Completed":
        time.sleep(5)  # Give async thread some time to complete.
        logger.info(f"Workflow status (async - after sleep): {engine.get_workflow_status(workflow.workflow_id)}")
    else:
        logger.info(f"Workflow completed asynchronously.")
    logger.info(f"Workflow Results: {workflow.get_results()}")
    logger.info(f"Workflow Context: {workflow.get_context()}")