<?php

namespace App\Workflows\Services;

use App\Workflows\Contracts\StepHandler;
use App\Workflows\Contracts\WorkflowRuntime;
use App\Workflows\Models\StepInstance;
use App\Workflows\Models\WorkflowInstance;
use App\Workflows\Models\WorkflowTemplate;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use InvalidArgumentException;

/**
 * Main workflow engine implementation
 */
class WorkflowEngine implements WorkflowRuntime
{
    public function __construct(
        private StepDataPersister $dataPersister
    ) {}

    /**
     * Ensure a workflow instance exists for the given subject
     */
    public function ensureInstance(object $subject, ?WorkflowTemplate $template = null): WorkflowInstance
    {
        // Check if instance already exists
        $existing = WorkflowInstance::where('subject_type', get_class($subject))
            ->where('subject_id', $subject->id)
            ->first();

        if ($existing) {
            return $existing;
        }

        // Find active template if not provided
        if (! $template) {
            $template = WorkflowTemplate::where('org_id', $subject->org_id)
                ->where('subject_type', get_class($subject))
                ->where('status', 'published')
                ->where('is_active', true)
                ->latest('version')
                ->first();

            if (! $template) {
                throw new Exception(__('workflow.errors.no_active_template', [
                    'model' => get_class($subject),
                ]));
            }
        }

        return DB::transaction(function () use ($subject, $template) {
            // Create workflow instance
            $instance = WorkflowInstance::create([
                'workflow_template_id' => $template->id,
                'org_id' => $subject->org_id,
                'subject_type' => get_class($subject),
                'subject_id' => $subject->id,
                'state' => 'running',
            ]);

            // Pre-create step instances with snapshots
            foreach ($template->stepTemplates as $stepTemplate) {
                StepInstance::create([
                    'workflow_instance_id' => $instance->id,
                    'step_template_id' => $stepTemplate->id,
                    'status' => 'pending',
                    'step_template_snapshot' => $this->snapshotFor($stepTemplate),
                ]);
            }

            return $instance;
        });
    }

    /**
     * Get the current step for a subject
     */
    public function currentStep(object $subject): ?StepInstance
    {
        $instance = WorkflowInstance::where('subject_type', get_class($subject))
            ->where('subject_id', $subject->id)
            ->first();

        return $instance?->currentStep();
    }

    /**
     * Approve a step with the given payload
     */
    public function approveStep(StepInstance $step, array $payload = [], ?string $note = null): void
    {
        if (! $step->isPending()) {
            throw new InvalidArgumentException(__('workflow.errors.only_pending_can_be_approved'));
        }

        $this->authorizeStep($step, 'approve');

        // Validate payload
        $rules = $this->extraRules($step);
        if (! empty($rules)) {
            $validator = Validator::make($payload, $rules);
            if ($validator->fails()) {
                throw new InvalidArgumentException(__('workflow.errors.validation_failed', [
                    'error' => $validator->errors()->first(),
                ]));
            }
        }

        DB::transaction(function () use ($step, $payload, $note) {
            // Update step instance
            $step->update([
                'status' => 'approved',
                'acted_by' => Auth::id(),
                'acted_at' => now(),
                'data' => $payload,
                'note' => $note,
            ]);

            // Persist data using handler or default persister
            if ($step->template->handler) {
                $this->callHandler($step->template->handler, 'persist', $step->template, $step->workflowInstance, $step, $payload);
            } else {
                $this->dataPersister->persist($step->workflowInstance, $step, $payload);
            }

            // Call after approve handler
            if ($step->template->handler) {
                $this->callHandler($step->template->handler, 'afterApprove', $step->template, $step->workflowInstance, $step);
            }

            // Check if workflow is complete
            $this->advanceOrComplete($step->workflowInstance);
        });
    }

    /**
     * Reject a step with the given reason
     */
    public function rejectStep(StepInstance $step, ?string $reason = null, array $payload = []): void
    {
        if (! $step->isPending()) {
            throw new InvalidArgumentException(__('workflow.errors.only_pending_can_be_rejected'));
        }

        $this->authorizeStep($step, 'reject');

        DB::transaction(function () use ($step, $reason, $payload) {
            // Update step instance
            $step->update([
                'status' => 'rejected',
                'acted_by' => Auth::id(),
                'acted_at' => now(),
                'data' => $payload,
                'note' => $reason,
            ]);

            // Mark workflow as rejected
            $step->workflowInstance->update(['state' => 'rejected']);
        });
    }

    /**
     * Cancel a workflow instance
     */
    public function cancel(WorkflowInstance $instance, ?string $reason = null): void
    {
        if ($instance->isCompleted()) {
            throw new InvalidArgumentException(__('workflow.errors.cannot_cancel_completed'));
        }

        $instance->update(['state' => 'canceled']);

        // Add note to current step if any
        $currentStep = $instance->currentStep();
        if ($currentStep) {
            $currentStep->update([
                'status' => 'skipped',
                'note' => __('workflow.activity.workflow_canceled', ['reason' => $reason]),
                'acted_by' => Auth::id(),
                'acted_at' => now(),
            ]);
        }
    }

    /**
     * Check authorization for step action
     */
    protected function authorizeStep(StepInstance $step, string $action): void
    {
        $user = Auth::user();

        if (! $user) {
            throw new InvalidArgumentException(__('workflow.errors.user_not_authenticated'));
        }

        // Get the step permission name based on step template ID
        $permission = $step->template->getPermissionName();

        // Set team context for permission check
        $orgId = $step->workflowInstance->org_id;
        setPermissionsTeamId($orgId);

        if (! $user->can($permission)) {
            throw new InvalidArgumentException(__('workflow.errors.no_permission', [
                'permission' => $permission,
            ]));
        }
    }

    /**
     * Get extra validation rules from handler
     */
    protected function extraRules(StepInstance $step): array
    {
        $baseRules = $step->template->validation ?? [];

        if ($step->template->handler) {
            $extraRules = $this->callHandler($step->template->handler, 'extraRules', $step->template, $step->workflowInstance);

            return array_merge($baseRules, $extraRules);
        }

        return $baseRules;
    }

    /**
     * Advance workflow or mark as complete
     */
    protected function advanceOrComplete(WorkflowInstance $instance): void
    {
        $nextStep = $this->nextPendingStep($instance);

        if (! $nextStep) {
            $instance->update(['state' => 'completed']);
        }
    }

    /**
     * Get the next pending step
     */
    protected function nextPendingStep(WorkflowInstance $instance): ?StepInstance
    {
        return $instance->stepInstances()
            ->where('status', 'pending')
            ->orderBy('id')
            ->first();
    }

    /**
     * Create snapshot of step template
     */
    protected function snapshotFor($stepTemplate): array
    {
        return [
            'system_key' => $stepTemplate->system_key,
            'label' => $stepTemplate->label,
            'position' => $stepTemplate->position,
            'type' => $stepTemplate->type,
            'form_definition_id' => $stepTemplate->form_definition_id,
            'validation' => $stepTemplate->validation,
        ];
    }

    /**
     * Call handler method with whitelist enforcement
     */
    protected function callHandler(string $class, string $method, ...$args)
    {
        $whitelist = config('workflows.handler_whitelist', []);

        if (! in_array($class, $whitelist)) {
            throw new InvalidArgumentException(__('workflow.errors.handler_not_whitelisted', [
                'class' => $class,
            ]));
        }

        if (! class_exists($class)) {
            throw new InvalidArgumentException(__('workflow.errors.handler_not_found', [
                'class' => $class,
            ]));
        }

        $handler = app($class);

        if (! $handler instanceof StepHandler) {
            throw new InvalidArgumentException(__('workflow.errors.handler_invalid', [
                'class' => $class,
            ]));
        }

        return $handler->{$method}(...$args);
    }
}
