<?php

namespace App\Services;

use App\Models\Application;
use App\Models\ApplicationRejection;
use App\Models\ApplicationStageHistory;
use App\Models\ApplicationSubmission;
use App\Models\ApprovalRequest;
use App\Models\AuditLog;
use App\Models\User;
use App\Models\WorkflowStage;
use App\Models\WorkflowTask;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use RuntimeException;

class ApplicationWorkflowProcessingService
{
    public function __construct(
        protected WorkflowEngineService $workflowEngine,
        protected ApplicationAssignmentService $assignmentService,
        protected OperationalNumberSequenceService $sequences,
    ) {}

    public function executeTransition(Application $application, WorkflowStage $targetStage, User $user, array $context = []): Application
    {
        $application->loadMissing(['currentStage', 'tenant', 'assignedUser']);
        $currentStage = $application->currentStage;
        if (! $currentStage) {
            throw new RuntimeException('Application has no active workflow stage.');
        }

        $transition = $this->workflowEngine->findTransition($currentStage, $targetStage);
        if (! $transition || ! $this->workflowEngine->canTransition($currentStage, $targetStage, $user)) {
            throw new RuntimeException("Unauthorized workflow transition from [{$currentStage->stage_name_en}] to [{$targetStage->stage_name_en}].");
        }

        $blockers = $this->workflowEngine->transitionBlockers($application, $transition);
        if ($blockers) {
            throw ValidationException::withMessages(['workflow' => $blockers]);
        }

        return DB::transaction(function () use ($application, $currentStage, $targetStage, $transition, $user, $context) {
            $now = now();
            $status = $transition->target_application_status
                ?: ($targetStage->stage_type === 'completion' ? 'completed' : 'processing');
            $completed = $targetStage->stage_type === 'completion';
            $previousAssignee = $application->assigned_user_id;
            $accountability = [];
            if ($targetStage->stage_code === 'TYPING') {
                $accountability = [
                    'typed_by' => $user->id,
                    'typing_started_at' => $now,
                    'typing_completed_at' => null,
                ];
            } elseif ($currentStage->stage_code === 'TYPING') {
                $accountability = [
                    'typed_by' => $user->id,
                    'typing_completed_at' => $now,
                ];
            }

            $application->update([
                'current_stage_id' => $targetStage->id,
                'assigned_department_id' => $targetStage->assigned_department_id,
                'status' => $status,
                'stage_entered_at' => $now,
                'sla_start_at' => $now,
                'warning_at' => $targetStage->sla_minutes > 0 ? $now->copy()->addMinutes((int) floor($targetStage->sla_minutes * .75)) : null,
                'due_at' => $targetStage->sla_minutes > 0 ? $now->copy()->addMinutes($targetStage->sla_minutes) : null,
                'sla_status' => 'on_track',
                'sla_paused_at' => null,
                'sla_pause_reason' => null,
                'completed_at' => $completed ? $now : $application->completed_at,
                'delivered_at' => $completed ? ($application->delivered_at ?? $now) : $application->delivered_at,
            ] + $accountability);

            WorkflowTask::withoutGlobalScopes()
                ->where('tenant_id', $application->tenant_id)
                ->where('source_type', 'application')
                ->where('source_id', $application->id)
                ->whereNotIn('status', ['completed', 'cancelled'])
                ->update(['status' => 'completed', 'completed_at' => $now]);

            $assigned = $this->assignmentService->assignForStage($application->fresh(['assignedUser']), $targetStage, $user);

            $stageHistory = ApplicationStageHistory::create([
                'application_id' => $application->id,
                'from_stage_id' => $currentStage->id,
                'to_stage_id' => $targetStage->id,
                'action_name' => $context['action_name'] ?? $transition->action_name_en,
                'performed_by' => $user->id,
                'remarks' => $context['remarks'] ?? null,
                'metadata_json' => [
                    'transition_id' => $transition->id,
                    'previous_assignee_id' => $previousAssignee,
                    'assigned_user_id' => $assigned?->id,
                    'is_return_path' => $transition->is_return_path,
                ],
            ]);

            if ($targetStage->requires_approval) {
                $this->createStageApproval($application, $targetStage, $user, $assigned, $stageHistory->id);
            }
            if ($transition->creates_task && ! $completed) {
                $this->createStageTask($application, $targetStage, $user, $assigned);
            }

            AuditLog::record('applications', 'application.stage_changed', $application, [
                'stage_id' => $currentStage->id,
                'assignee_id' => $previousAssignee,
            ], [
                'stage_id' => $targetStage->id,
                'assignee_id' => $assigned?->id,
                'status' => $status,
            ], $user);

            return $application->fresh(['currentStage', 'assignedUser']);
        });
    }

    public function recordSubmission(Application $application, User $user, array $data): ApplicationSubmission
    {
        return DB::transaction(function () use ($application, $user, $data) {
            $attemptNum = ApplicationSubmission::where('application_id', $application->id)->lockForUpdate()->count() + 1;
            $submission = ApplicationSubmission::create([
                'application_id' => $application->id,
                'attempt_number' => $attemptNum,
                'authority_code' => $data['authority_code'] ?? $application->service?->authority?->code ?? 'UAE-AUTHORITY',
                'portal_name' => $data['portal_name'] ?? 'Government Portal',
                'government_reference' => $data['government_reference'] ?? null,
                'request_number' => $data['request_number'] ?? null,
                'transaction_number' => $data['transaction_number'] ?? null,
                'receipt_number' => $data['receipt_number'] ?? null,
                'gov_fee_paid' => $data['gov_fee_paid'] ?? 0,
                'status' => 'submitted',
                'portal_status' => $data['portal_status'] ?? 'submitted',
                'remarks' => $data['remarks'] ?? null,
                'attachment_path' => $data['attachment_path'] ?? null,
                'submitted_by' => $user->id,
            ]);

            $application->update(['status' => 'submitted']);
            AuditLog::record('applications', 'application.submitted', $application, null, null, $user);

            if ($application->currentStage?->stage_code === 'GOVERNMENT_SUBMISSION') {
                $target = WorkflowStage::where('service_id', $application->service_id)->where('stage_code', 'GOVERNMENT_PROCESSING')->firstOrFail();
                $this->executeTransition($application->fresh(['currentStage', 'tenant']), $target, $user, [
                    'action_name' => 'Government Submission Attempt #'.$attemptNum,
                    'remarks' => $data['remarks'] ?? 'Submitted to '.$submission->authority_code,
                ]);
            }

            return $submission;
        });
    }

    public function recordGovernmentDecision(Application $application, User $user, string $decision, ?string $reason = null): Application
    {
        if (! in_array($decision, ['approved', 'returned', 'rejected'], true)) {
            throw ValidationException::withMessages(['decision' => 'Invalid government decision.']);
        }

        return DB::transaction(function () use ($application, $user, $decision, $reason) {
            $submission = ApplicationSubmission::where('application_id', $application->id)
                ->orderByDesc('attempt_number')
                ->orderByDesc('id')
                ->lockForUpdate()
                ->first();
            if (! $submission) {
                throw ValidationException::withMessages(['government' => 'Record a government submission before its response.']);
            }

            $submission->update([
                'status' => $decision,
                'portal_status' => $decision,
                'return_reason' => $decision === 'returned' ? $reason : $submission->return_reason,
                'remarks' => $reason ?: $submission->remarks,
                'response_at' => now(),
                'approved_at' => $decision === 'approved' ? now() : null,
            ]);

            $targetCode = ['approved' => 'GOVERNMENT_APPROVED', 'returned' => 'GOVERNMENT_RETURNED', 'rejected' => 'REJECTED'][$decision];
            if ($application->currentStage?->stage_code === 'GOVERNMENT_PROCESSING') {
                $target = WorkflowStage::where('service_id', $application->service_id)->where('stage_code', $targetCode)->firstOrFail();
                $application = $this->executeTransition($application->fresh(['currentStage', 'tenant']), $target, $user, [
                    'action_name' => 'Government '.ucfirst($decision),
                    'remarks' => $reason,
                ]);
            } else {
                $application->update(['status' => $decision]);
            }

            AuditLog::record('applications', 'application.government_'.$decision, $application, null, null, $user);

            return $application;
        });
    }

    public function recordReturn(Application $application, User $user, string $reason): Application
    {
        return $this->recordGovernmentDecision($application, $user, 'returned', $reason);
    }

    public function recordRejection(Application $application, User $user, string $reasonText, string $type = 'government'): Application
    {
        ApplicationRejection::create([
            'application_id' => $application->id,
            'rejection_type' => $type,
            'reason_en' => $reasonText,
            'rejected_by' => $user->id,
        ]);

        if ($application->submissions()->exists()) {
            return $this->recordGovernmentDecision($application, $user, 'rejected', $reasonText);
        }

        $application->update(['status' => 'rejected']);
        AuditLog::record('applications', 'application.rejected', $application, null, null, $user);

        return $application;
    }

    private function createStageApproval(Application $application, WorkflowStage $stage, User $actor, ?User $assignee, int $stageHistoryId): void
    {
        $payloadMarker = '%"stage_history_id":'.$stageHistoryId.'%';
        $exists = ApprovalRequest::withoutGlobalScopes()
            ->where('tenant_id', $application->tenant_id)
            ->where('type', 'workflow_stage')
            ->where('subject_type', 'application')
            ->where('subject_id', $application->id)
            ->where('payload', 'like', $payloadMarker)
            ->where('status', 'pending')
            ->exists();
        if ($exists) {
            return;
        }

        ApprovalRequest::withoutGlobalScopes()->create([
            'tenant_id' => $application->tenant_id,
            'branch_id' => $application->branch_id,
            'approval_number' => $this->sequences->next($application->tenant, 'approval'),
            'type' => 'workflow_stage',
            'subject_type' => 'application',
            'subject_id' => $application->id,
            'requested_by' => $actor->id,
            'assigned_to' => $assignee?->id,
            'reason' => "Quality approval required for {$application->application_number} at {$stage->stage_name_en}.",
            'payload' => ['stage_id' => $stage->id, 'stage_code' => $stage->stage_code, 'stage_history_id' => $stageHistoryId],
            'status' => 'pending',
            'expires_at' => $stage->sla_minutes > 0 ? now()->addMinutes($stage->sla_minutes) : null,
        ]);
    }

    private function createStageTask(Application $application, WorkflowStage $stage, User $actor, ?User $assignee): void
    {
        WorkflowTask::withoutGlobalScopes()->create([
            'tenant_id' => $application->tenant_id,
            'branch_id' => $application->branch_id,
            'task_number' => $this->sequences->next($application->tenant, 'task'),
            'source_type' => 'application',
            'source_id' => $application->id,
            'title' => $stage->stage_name_en.' — '.$application->application_number,
            'description' => $stage->instructions,
            'priority' => in_array($application->priority, ['urgent', 'vip'], true) ? 'urgent' : 'normal',
            'status' => 'open',
            'assigned_user_id' => $assignee?->id,
            'created_by' => $actor->id,
            'due_at' => $application->due_at,
        ]);
    }
}
