"""Workflow execution engine — step processing, conditions, approvals.

Processes workflow instances by evaluating steps sequentially.
Supports step types: action, approval, notification, condition.
Integrates with the event bus for event-triggered workflows.
"""

from __future__ import annotations

import logging
import uuid
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.event_bus import get_event_bus
from app.models.notification import Notification
from app.models.workflow import Workflow, WorkflowInstance
from app.services.workflow_service import (
    _instance_to_dict,
    _log_step_history,
    create_instance,
    find_workflows_for_event,
)

logger = logging.getLogger(__name__)


class WorkflowEngine:
    """Processes workflow instances through their defined steps.

    Step types:
    - action: Executes a configured action (e.g. create entity, send notification)
    - approval: Pauses and waits for user approve/reject via API
    - notification: Sends a notification to specified users
    - condition: Evaluates a condition and branches accordingly
    """

    def __init__(self, db: AsyncSession, tenant_id: uuid.UUID):
        self.db = db
        self.tenant_id = tenant_id

    async def process_step(self, instance: WorkflowInstance) -> dict[str, Any]:
        """Process the current step of a workflow instance.

        For action/notification/condition steps: executes and advances.
        For approval steps: sets status to in_progress and waits.
        Returns the updated instance dict.
        """
        wf_result = await self.db.execute(
            select(Workflow).where(Workflow.id == instance.workflow_id)
        )
        workflow = wf_result.scalar_one_or_none()
        if workflow is None:
            return {"error": "Workflow not found", "status_code": 404}

        steps = workflow.steps or []
        if instance.current_step_index >= len(steps):
            instance.status = "completed"
            instance.completed_at = datetime.now(UTC)
            await self.db.flush()
            return _instance_to_dict(instance)

        step = steps[instance.current_step_index]
        step_type = step.get("type", "action")

        # Log step entry
        await _log_step_history(
            self.db,
            self.tenant_id,
            instance.id,
            step_index=instance.current_step_index,
            step_type=step_type,
            action="processing",
            details={"step_name": step.get("name")},
        )

        if step_type == "approval":
            # Approval steps pause and wait for user input
            if instance.status == "pending":
                instance.status = "in_progress"
            await self.db.flush()
            return _instance_to_dict(instance)

        elif step_type == "notification":
            return await self._process_notification(instance, step)

        elif step_type == "condition":
            return await self._process_condition(instance, step, steps)

        elif step_type == "action":
            return await self._process_action(instance, step, steps)

        else:
            return {"error": f"Unknown step type: {step_type}", "status_code": 400}

    async def _process_action(
        self, instance: WorkflowInstance, step: dict, steps: list
    ) -> dict[str, Any]:
        """Process an action step — executes the configured action and advances."""
        config = step.get("config", {})
        action_type = config.get("action_type", "noop")

        # Execute action based on type
        if action_type == "create_notification":
            user_id = config.get("user_id") or (
                str(instance.initiated_by) if instance.initiated_by else None
            )
            if user_id:
                notification = Notification(
                    tenant_id=self.tenant_id,
                    user_id=uuid.UUID(user_id),
                    type=config.get("notification_type", "workflow_action"),
                    title=config.get("title", "Workflow notification"),
                    body=config.get("body", ""),
                )
                self.db.add(notification)
                await self.db.flush()

        elif action_type == "noop":
            pass  # No operation — just advance

        # Log action executed
        await _log_step_history(
            self.db,
            self.tenant_id,
            instance.id,
            step_index=instance.current_step_index,
            step_type="action",
            action="executed",
            details={"action_type": action_type},
        )

        # Advance to next step
        next_idx = instance.current_step_index + 1
        if next_idx >= len(steps):
            instance.status = "completed"
            instance.completed_at = datetime.now(UTC)
        else:
            instance.current_step_index = next_idx
            instance.status = "in_progress"

        await self.db.flush()
        return _instance_to_dict(instance)

    async def _process_notification(self, instance: WorkflowInstance, step: dict) -> dict[str, Any]:
        """Process a notification step — sends notification and advances."""
        config = step.get("config", {})
        user_id = config.get("user_id") or (
            str(instance.initiated_by) if instance.initiated_by else None
        )

        if user_id:
            notification = Notification(
                tenant_id=self.tenant_id,
                user_id=uuid.UUID(user_id),
                type=config.get("notification_type", "workflow_notification"),
                title=config.get("title", "Workflow notification"),
                body=config.get("body", ""),
            )
            self.db.add(notification)
            await self.db.flush()

        await _log_step_history(
            self.db,
            self.tenant_id,
            instance.id,
            step_index=instance.current_step_index,
            step_type="notification",
            action="sent",
            details={"user_id": user_id},
        )

        # Advance — notification steps auto-advance
        wf_result = await self.db.execute(
            select(Workflow).where(Workflow.id == instance.workflow_id)
        )
        workflow = wf_result.scalar_one_or_none()
        steps = workflow.steps if workflow else []
        next_idx = instance.current_step_index + 1
        if next_idx >= len(steps):
            instance.status = "completed"
            instance.completed_at = datetime.now(UTC)
        else:
            instance.current_step_index = next_idx

        await self.db.flush()
        return _instance_to_dict(instance)

    async def _process_condition(
        self, instance: WorkflowInstance, step: dict, steps: list
    ) -> dict[str, Any]:
        """Process a condition step — evaluates condition and branches.

        Config format:
        {
            "field": "context_key",
            "operator": "eq|ne|gt|lt|contains",
            "value": "expected_value",
            "on_true_step": optional_index,
            "on_false_step": optional_index
        }
        """
        config = step.get("config", {})
        field = config.get("field", "")
        operator = config.get("operator", "eq")
        expected = config.get("value")
        actual = instance.context.get(field)

        condition_met = False
        if operator == "eq":
            condition_met = actual == expected
        elif operator == "ne":
            condition_met = actual != expected
        elif operator == "gt":
            condition_met = actual is not None and expected is not None and actual > expected
        elif operator == "lt":
            condition_met = actual is not None and expected is not None and actual < expected
        elif operator == "contains":
            condition_met = (
                actual is not None and expected in actual
                if isinstance(actual, str | list)
                else False
            )

        await _log_step_history(
            self.db,
            self.tenant_id,
            instance.id,
            step_index=instance.current_step_index,
            step_type="condition",
            action="evaluated",
            details={"field": field, "operator": operator, "condition_met": condition_met},
        )

        # Branch or advance
        if condition_met and "on_true_step" in config:
            instance.current_step_index = config["on_true_step"]
        elif not condition_met and "on_false_step" in config:
            instance.current_step_index = config["on_false_step"]
        else:
            next_idx = instance.current_step_index + 1
            if next_idx >= len(steps):
                instance.status = "completed"
                instance.completed_at = datetime.now(UTC)
            else:
                instance.current_step_index = next_idx

        await self.db.flush()
        return _instance_to_dict(instance)


async def handle_event(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    event_name: str,
    payload: dict[str, Any],
) -> list[dict[str, Any]]:
    """Handle an event by starting matching workflow instances.

    Called by the event bus integration. Finds all active workflows
    with trigger_event matching event_name and creates instances.
    """
    workflows = await find_workflows_for_event(db, tenant_id, event_name)
    instances: list[dict[str, Any]] = []
    for wf in workflows:
        inst = await create_instance(
            db,
            tenant_id,
            uuid.UUID(payload.get("user_id", str(uuid.uuid4())))
            if payload.get("user_id")
            else None or uuid.uuid4(),
            str(wf.id),
            context=payload,
        )
        if inst:
            instances.append(inst)
    return instances


def register_workflow_event_handlers() -> None:
    """Register event bus handlers for workflow triggers.

    Subscribes to the event bus to auto-start workflows when events fire.
    Should be called during application startup.
    """
    event_bus = get_event_bus()

    async def _workflow_event_handler(payload: dict[str, Any]) -> None:
        """Handle events that may trigger workflows."""
        from app.core.db import create_db_session

        tenant_id_str = payload.get("tenant_id")
        event_name = payload.get("event", "")
        if not tenant_id_str or not event_name:
            return

        tenant_id = uuid.UUID(tenant_id_str)
        async with create_db_session(tenant_id) as db:
            await handle_event(db, tenant_id, event_name, payload)

    # Subscribe to common events
    for event_name in ("user.created", "contact.created"):
        event_bus.subscribe(event_name, _workflow_event_handler)
