<?php
declare(strict_types=1);

namespace App\Modules\Workflow\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\Workflow\Models\WorkflowDefinition;
use App\Modules\Workflow\Models\WorkflowInstance;
use App\Modules\Workflow\Models\WorkflowTransitionLog;

final class WorkflowEngine
{
    /**
     * Create a new workflow instance for an entity.
     */
    public static function createInstance(
        string $workflowCode,
        string $entityType,
        int $entityId,
        ?array $stateData = null
    ): WorkflowInstance {
        $definition = WorkflowDefinition::findByCode($workflowCode);
        if (!$definition) {
            throw new \RuntimeException("Workflow definition not found: {$workflowCode}");
        }

        $initialState = $definition->getInitialState();
        if ($initialState === null) {
            throw new \RuntimeException("No initial state defined for workflow: {$workflowCode}");
        }

        $instance = WorkflowInstance::create([
            'workflow_definition_id' => (int) $definition->id,
            'workflow_code'          => $workflowCode,
            'entity_type'            => $entityType,
            'entity_id'              => $entityId,
            'current_state'          => $initialState,
            'state_entered_at'       => date('Y-m-d H:i:s'),
            'state_data_json'        => $stateData !== null ? json_encode($stateData, JSON_UNESCAPED_UNICODE) : null,
            'is_completed'           => 0,
        ]);

        Logger::info("Workflow instance created", [
            'workflow_code' => $workflowCode,
            'entity_type'   => $entityType,
            'entity_id'     => $entityId,
            'initial_state' => $initialState,
            'instance_id'   => (int) $instance->id,
        ]);

        $eventData = [
            'instance_id'   => (int) $instance->id,
            'workflow_code'  => $workflowCode,
            'entity_type'    => $entityType,
            'entity_id'      => $entityId,
            'current_state'  => $initialState,
        ];
        EventBus::dispatch('workflow.created', $eventData);

        return $instance;
    }

    /**
     * Execute a transition on a workflow instance.
     */
    public static function transition(
        int $instanceId,
        string $transitionName,
        ?string $notes = null,
        string $triggerType = 'manual'
    ): bool {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $instance = WorkflowInstance::find($instanceId);
        if (!$instance) {
            throw new \RuntimeException("Workflow instance not found: {$instanceId}");
        }

        if ($instance->is_completed) {
            throw new \RuntimeException("Workflow instance is already completed");
        }

        $definition = $instance->getDefinition();
        if (!$definition) {
            throw new \RuntimeException("Workflow definition not found for instance: {$instanceId}");
        }

        $transition = $definition->findTransition($transitionName);
        if (!$transition) {
            throw new \RuntimeException("Transition not found: {$transitionName}");
        }

        if (($transition['from'] ?? '') !== $instance->current_state) {
            throw new \RuntimeException(
                "Cannot execute transition '{$transitionName}' from state '{$instance->current_state}'. Expected state: '{$transition['from']}'"
            );
        }

        // Evaluate guards
        $guards = $transition['guards'] ?? [];
        $guardResults = [];
        foreach ($guards as $guard) {
            $result = WorkflowGuardEvaluator::evaluate($guard, $instance);
            $guardResults[] = [
                'type'   => $guard['type'] ?? 'unknown',
                'value'  => $guard['value'] ?? '',
                'passed' => $result,
            ];
            if (!$result) {
                Logger::warning("Workflow guard failed", [
                    'instance_id'    => $instanceId,
                    'transition'     => $transitionName,
                    'guard_type'     => $guard['type'] ?? 'unknown',
                    'guard_value'    => $guard['value'] ?? '',
                ]);
                throw new \RuntimeException(
                    "Guard failed for transition '{$transitionName}': {$guard['type']} — {$guard['value']}"
                );
            }
        }

        $fromState = $instance->current_state;
        $toState = $transition['to'];

        // Check if target state is terminal
        $states = $definition->getStates();
        $isTerminal = ($states[$toState]['type'] ?? '') === 'terminal';

        // Begin transaction
        $db->beginTransaction();
        try {
            // Update instance
            $updateData = [
                'current_state'    => $toState,
                'state_entered_at' => date('Y-m-d H:i:s'),
                'updated_at'       => date('Y-m-d H:i:s'),
            ];
            if ($isTerminal) {
                $updateData['is_completed'] = 1;
                $updateData['completed_at'] = date('Y-m-d H:i:s');
            }

            $db->update('workflow_instances', $updateData, '`id` = ?', [$instanceId]);

            // Log transition
            WorkflowTransitionLog::create([
                'workflow_instance_id'     => $instanceId,
                'from_state'               => $fromState,
                'to_state'                 => $toState,
                'transition_name'          => $transitionName,
                'triggered_by_employee_id' => $employee ? (int) ($employee->id ?? 0) : null,
                'trigger_type'             => $triggerType,
                'guard_results_json'       => json_encode($guardResults, JSON_UNESCAPED_UNICODE),
                'notes'                    => $notes,
                'created_at'               => date('Y-m-d H:i:s'),
            ]);

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }

        // Execute actions (after commit — these are side effects)
        $actions = $transition['actions'] ?? [];
        foreach ($actions as $action) {
            self::executeAction($action, $instance, $toState);
        }

        // Dispatch workflow event
        $eventData = [
            'instance_id'    => $instanceId,
            'workflow_code'   => $instance->workflow_code,
            'entity_type'     => $instance->entity_type,
            'entity_id'       => (int) $instance->entity_id,
            'from_state'      => $fromState,
            'to_state'        => $toState,
            'transition_name' => $transitionName,
            'trigger_type'    => $triggerType,
            'is_completed'    => $isTerminal,
        ];
        EventBus::dispatch('workflow.transitioned', $eventData);

        Logger::info("Workflow transition executed", [
            'instance_id' => $instanceId,
            'transition'  => $transitionName,
            'from'        => $fromState,
            'to'          => $toState,
        ]);

        return true;
    }

    /**
     * Get available transitions from current state.
     */
    public static function getAvailableTransitions(int $instanceId): array
    {
        $instance = WorkflowInstance::find($instanceId);
        if (!$instance || $instance->is_completed) {
            return [];
        }

        $definition = $instance->getDefinition();
        if (!$definition) {
            return [];
        }

        $available = [];
        $transitions = $definition->getTransitionsFrom($instance->current_state);

        foreach ($transitions as $transition) {
            $autoTrigger = $transition['trigger'] ?? 'manual';
            if ($autoTrigger === 'automatic') {
                continue; // Automatic transitions are not shown as available manual actions
            }

            $canExecute = true;
            $guards = $transition['guards'] ?? [];
            foreach ($guards as $guard) {
                if (!WorkflowGuardEvaluator::evaluate($guard, $instance)) {
                    $canExecute = false;
                    break;
                }
            }

            $states = $definition->getStates();
            $toStateInfo = $states[$transition['to']] ?? [];

            $available[] = [
                'name'        => $transition['name'],
                'to_state'    => $transition['to'],
                'to_label_ar' => $toStateInfo['label_ar'] ?? $transition['to'],
                'to_label_en' => $toStateInfo['label_en'] ?? $transition['to'],
                'can_execute' => $canExecute,
                'guards'      => $guards,
            ];
        }

        return $available;
    }

    /**
     * Execute a transition by entity type/ID (finds the active instance automatically).
     */
    public static function transitionByEntity(
        string $entityType,
        int $entityId,
        string $transitionName,
        ?string $notes = null,
        string $triggerType = 'manual',
        ?string $workflowCode = null
    ): bool {
        $instance = WorkflowInstance::findForEntity($entityType, $entityId, $workflowCode);
        if (!$instance) {
            throw new \RuntimeException("No active workflow instance for {$entityType}#{$entityId}");
        }
        return self::transition((int) $instance->id, $transitionName, $notes, $triggerType);
    }

    /**
     * Get available transitions for an entity (without needing instance ID).
     */
    public static function getAvailableTransitionsForEntity(string $entityType, int $entityId, ?string $workflowCode = null): array
    {
        $instance = WorkflowInstance::findForEntity($entityType, $entityId, $workflowCode);
        if (!$instance) {
            return [];
        }
        return self::getAvailableTransitions((int) $instance->id);
    }

    /**
     * Check if a workflow definition exists for the given code.
     */
    public static function hasDefinition(string $workflowCode): bool
    {
        return WorkflowDefinition::findByCode($workflowCode) !== null;
    }

    /**
     * Ensure a workflow instance exists for an entity (create if missing).
     */
    public static function ensureInstance(string $workflowCode, string $entityType, int $entityId): WorkflowInstance
    {
        $instance = WorkflowInstance::findForEntity($entityType, $entityId, $workflowCode);
        if ($instance) {
            return $instance;
        }
        return self::createInstance($workflowCode, $entityType, $entityId);
    }

    /**
     * Get current state for an entity.
     */
    public static function getCurrentState(string $entityType, int $entityId, ?string $workflowCode = null): ?string
    {
        $instance = WorkflowInstance::findForEntity($entityType, $entityId, $workflowCode);
        return $instance ? $instance->current_state : null;
    }

    /**
     * Get full transition history for an instance.
     */
    public static function getHistory(int $instanceId): array
    {
        return WorkflowTransitionLog::getForInstance($instanceId);
    }

    /**
     * Check for timed-out workflow instances across all workflows.
     */
    public static function checkTimeouts(): array
    {
        $timedOut = [];
        $definitions = WorkflowDefinition::allActive();

        foreach ($definitions as $defRow) {
            $definition = new WorkflowDefinition($defRow);
            $definition->exists = true;
            $transitions = $definition->getTransitions();

            foreach ($transitions as $transition) {
                if (($transition['trigger'] ?? '') !== 'automatic') {
                    continue;
                }

                $guards = $transition['guards'] ?? [];
                foreach ($guards as $guard) {
                    if (($guard['type'] ?? '') === 'timeout' && isset($guard['days'])) {
                        $days = (int) $guard['days'];
                        $fromState = $transition['from'];
                        $instances = WorkflowInstance::getTimedOut($defRow['workflow_code'], $fromState, $days);

                        foreach ($instances as $inst) {
                            $timedOut[] = [
                                'instance_id'     => (int) $inst['id'],
                                'workflow_code'    => $inst['workflow_code'],
                                'entity_type'      => $inst['entity_type'],
                                'entity_id'        => (int) $inst['entity_id'],
                                'current_state'    => $inst['current_state'],
                                'transition_name'  => $transition['name'],
                                'timeout_days'     => $days,
                                'state_entered_at' => $inst['state_entered_at'],
                            ];
                        }
                    }
                }
            }
        }

        return $timedOut;
    }

    /**
     * Process all timed-out transitions automatically.
     */
    public static function processTimeouts(): array
    {
        $timedOut = self::checkTimeouts();
        $processed = [];

        foreach ($timedOut as $item) {
            try {
                self::transition(
                    $item['instance_id'],
                    $item['transition_name'],
                    'انتقال تلقائي بسبب انتهاء المهلة',
                    'timeout'
                );
                $processed[] = $item;
            } catch (\Throwable $e) {
                Logger::error("Failed to process timeout transition", [
                    'instance_id' => $item['instance_id'],
                    'transition'  => $item['transition_name'],
                    'error'       => $e->getMessage(),
                ]);
            }
        }

        return $processed;
    }

    /**
     * Execute a transition action.
     */
    private static function executeAction(array $action, WorkflowInstance $instance, string $newState): void
    {
        $type = $action['type'] ?? '';
        $value = $action['value'] ?? '';

        switch ($type) {
            case 'event':
                $eventData = [
                    'workflow_instance_id' => (int) $instance->id,
                    'workflow_code'        => $instance->workflow_code,
                    'entity_type'          => $instance->entity_type,
                    'entity_id'            => (int) $instance->entity_id,
                    'new_state'            => $newState,
                ];
                EventBus::dispatch($value, $eventData);
                break;

            case 'notification':
                EventBus::dispatch('notification.send', [
                    'template'    => $value,
                    'entity_type' => $instance->entity_type,
                    'entity_id'   => (int) $instance->entity_id,
                ]);
                break;

            default:
                Logger::warning("Unknown workflow action type: {$type}", [
                    'instance_id' => (int) $instance->id,
                    'value'       => $value,
                ]);
                break;
        }
    }
}