# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Interface for Automation2 feature - Workflows, Runs, Human Tasks.
Uses PostgreSQL poweron_automation2 database.
"""

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


def _make_json_serializable(obj: Any) -> Any:
    """
    Recursively convert bytes to base64 strings so structures can be JSON-serialized
    for storage in JSONB columns.
    """
    if isinstance(obj, bytes):
        return base64.b64encode(obj).decode("ascii")
    if isinstance(obj, dict):
        return {k: _make_json_serializable(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_make_json_serializable(v) for v in obj]
    return obj

from modules.datamodels.datamodelUam import User
from modules.features.automation2.datamodelFeatureAutomation2 import (
    Automation2Workflow,
    Automation2WorkflowRun,
    Automation2HumanTask,
)
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG

logger = logging.getLogger(__name__)


def getAutomation2Interface(
    currentUser: User,
    mandateId: str,
    featureInstanceId: str,
) -> "Automation2Objects":
    """Factory for Automation2 interface with user context."""
    return Automation2Objects(
        currentUser=currentUser,
        mandateId=mandateId,
        featureInstanceId=featureInstanceId,
    )


class Automation2Objects:
    """Interface for Automation2 database operations."""

    def __init__(
        self,
        currentUser: User,
        mandateId: str,
        featureInstanceId: str,
    ):
        self.currentUser = currentUser
        self.mandateId = mandateId
        self.featureInstanceId = featureInstanceId
        self.userId = currentUser.id if currentUser else None
        self._init_db()
        if hasattr(self.db, "updateContext") and self.userId:
            self.db.updateContext(self.userId)

    def _init_db(self):
        """Initialize database connection to poweron_automation2."""
        dbHost = APP_CONFIG.get("DB_HOST", "localhost")
        dbDatabase = "poweron_automation2"
        dbUser = APP_CONFIG.get("DB_USER")
        dbPassword = APP_CONFIG.get("DB_PASSWORD_SECRET") or APP_CONFIG.get("DB_PASSWORD")
        dbPort = int(APP_CONFIG.get("DB_PORT", 5432))
        self.db = DatabaseConnector(
            dbHost=dbHost,
            dbDatabase=dbDatabase,
            dbUser=dbUser,
            dbPassword=dbPassword,
            dbPort=dbPort,
            userId=self.userId,
        )
        logger.debug("Automation2 database initialized for user %s", self.userId)

    # -------------------------------------------------------------------------
    # Workflow CRUD
    # -------------------------------------------------------------------------

    def getWorkflows(self) -> List[Dict[str, Any]]:
        """Get all workflows for this mandate and feature instance."""
        if not self.db._ensureTableExists(Automation2Workflow):
            return []
        records = self.db.getRecordset(
            Automation2Workflow,
            recordFilter={
                "mandateId": self.mandateId,
                "featureInstanceId": self.featureInstanceId,
            },
        )
        return [dict(r) for r in records] if records else []

    def getWorkflow(self, workflowId: str) -> Optional[Dict[str, Any]]:
        """Get a single workflow by ID."""
        if not self.db._ensureTableExists(Automation2Workflow):
            return None
        records = self.db.getRecordset(
            Automation2Workflow,
            recordFilter={
                "id": workflowId,
                "mandateId": self.mandateId,
                "featureInstanceId": self.featureInstanceId,
            },
        )
        if not records:
            return None
        return dict(records[0])

    def createWorkflow(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Create a new workflow."""
        if "id" not in data or not data.get("id"):
            data["id"] = str(uuid.uuid4())
        data["mandateId"] = self.mandateId
        data["featureInstanceId"] = self.featureInstanceId
        created = self.db.recordCreate(Automation2Workflow, data)
        return dict(created)

    def updateWorkflow(self, workflowId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Update an existing workflow."""
        existing = self.getWorkflow(workflowId)
        if not existing:
            return None
        # Don't overwrite mandateId/featureInstanceId
        data.pop("mandateId", None)
        data.pop("featureInstanceId", None)
        updated = self.db.recordModify(Automation2Workflow, workflowId, data)
        return dict(updated)

    def deleteWorkflow(self, workflowId: str) -> bool:
        """Delete a workflow."""
        existing = self.getWorkflow(workflowId)
        if not existing:
            return False
        self.db.recordDelete(Automation2Workflow, workflowId)
        return True

    # -------------------------------------------------------------------------
    # Workflow Runs
    # -------------------------------------------------------------------------

    def createRun(self, workflowId: str, nodeOutputs: Dict = None, context: Dict = None) -> Dict[str, Any]:
        """Create a new workflow run."""
        data = {
            "id": str(uuid.uuid4()),
            "workflowId": workflowId,
            "status": "running",
            "nodeOutputs": _make_json_serializable(nodeOutputs or {}),
            "currentNodeId": None,
            "context": context or {},
        }
        created = self.db.recordCreate(Automation2WorkflowRun, data)
        return dict(created)

    def getRun(self, runId: str) -> Optional[Dict[str, Any]]:
        """Get a run by ID."""
        if not self.db._ensureTableExists(Automation2WorkflowRun):
            return None
        records = self.db.getRecordset(
            Automation2WorkflowRun,
            recordFilter={"id": runId},
        )
        if not records:
            return None
        return dict(records[0])

    def updateRun(
        self,
        runId: str,
        status: str = None,
        nodeOutputs: Dict = None,
        currentNodeId: str = None,
        context: Dict = None,
    ) -> Optional[Dict[str, Any]]:
        """Update a run."""
        run = self.getRun(runId)
        if not run:
            return None
        updates = {}
        if status is not None:
            updates["status"] = status
        if nodeOutputs is not None:
            updates["nodeOutputs"] = _make_json_serializable(nodeOutputs)
        if currentNodeId is not None:
            updates["currentNodeId"] = currentNodeId
        if context is not None:
            updates["context"] = context
        if not updates:
            return run
        updated = self.db.recordModify(Automation2WorkflowRun, runId, updates)
        return dict(updated)

    def getRunsByWorkflow(self, workflowId: str) -> List[Dict[str, Any]]:
        """Get all runs for a workflow."""
        if not self.db._ensureTableExists(Automation2WorkflowRun):
            return []
        records = self.db.getRecordset(
            Automation2WorkflowRun,
            recordFilter={"workflowId": workflowId},
        )
        return [dict(r) for r in records] if records else []

    def getRunsWaitingForEmail(self) -> List[Dict[str, Any]]:
        """Get all paused runs waiting for a new email (for background poller)."""
        if not self.db._ensureTableExists(Automation2WorkflowRun):
            return []
        records = self.db.getRecordset(
            Automation2WorkflowRun,
            recordFilter={"status": "paused"},
        )
        if not records:
            return []
        result = []
        for r in records:
            rec = dict(r)
            ctx = rec.get("context") or {}
            if ctx.get("waitReason") == "email":
                result.append(rec)
        return result

    # -------------------------------------------------------------------------
    # Human Tasks
    # -------------------------------------------------------------------------

    def createTask(
        self,
        runId: str,
        workflowId: str,
        nodeId: str,
        nodeType: str,
        config: Dict[str, Any],
        assigneeId: str = None,
    ) -> Dict[str, Any]:
        """Create a human task."""
        data = {
            "id": str(uuid.uuid4()),
            "runId": runId,
            "workflowId": workflowId,
            "nodeId": nodeId,
            "nodeType": nodeType,
            "config": config,
            "assigneeId": assigneeId,
            "status": "pending",
            "result": None,
        }
        created = self.db.recordCreate(Automation2HumanTask, data)
        return dict(created)

    def getTask(self, taskId: str) -> Optional[Dict[str, Any]]:
        """Get a task by ID."""
        if not self.db._ensureTableExists(Automation2HumanTask):
            return None
        records = self.db.getRecordset(
            Automation2HumanTask,
            recordFilter={"id": taskId},
        )
        if not records:
            return None
        return dict(records[0])

    def updateTask(self, taskId: str, status: str = None, result: Dict = None) -> Optional[Dict[str, Any]]:
        """Update a task (e.g. complete with result)."""
        task = self.getTask(taskId)
        if not task:
            return None
        updates = {}
        if status is not None:
            updates["status"] = status
        if result is not None:
            updates["result"] = result
        if not updates:
            return task
        updated = self.db.recordModify(Automation2HumanTask, taskId, updates)
        return dict(updated)

    def getTasks(
        self,
        workflowId: str = None,
        runId: str = None,
        status: str = None,
        assigneeId: str = None,
    ) -> List[Dict[str, Any]]:
        """Get tasks with optional filters. AssigneeId filters to that user; None returns all."""
        if not self.db._ensureTableExists(Automation2HumanTask):
            return []
        rf = {}
        if workflowId:
            rf["workflowId"] = workflowId
        if runId:
            rf["runId"] = runId
        if status:
            rf["status"] = status
        if assigneeId:
            rf["assigneeId"] = assigneeId
        records = self.db.getRecordset(
            Automation2HumanTask,
            recordFilter=rf if rf else None,
        )
        items = [dict(r) for r in records] if records else []
        workflows = {w["id"]: w for w in self.getWorkflows()}
        filtered = [t for t in items if t.get("workflowId") in workflows]
        return filtered
