<?php

namespace App\Jobs;

use App\Crm\SendLeadExportNotification;
use App\Models\LeadExport;
use Carbon\Carbon;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Mtc\Crm\Contracts\EnquiryAction;
use Mtc\Crm\Contracts\EnquiryActionModel;
use Mtc\Crm\Facades\Enquiries;
use Mtc\Crm\Contracts\EnquiryModel;
use Mtc\Crm\Traits\RetrievesCustomerId;

class ProcessIncomingEnquiryJob implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;
    use RetrievesCustomerId;
    /**
     * enquiry_actions.context is VARCHAR(500); longer values make the update itself
     * throw and take the whole job down instead of recording the failure.
     */
    private const CONTEXT_MAX_LENGTH = 500;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(protected EnquiryModel $enquiry)
    {
        $this->onQueue('emails');
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $this->enquiry->refresh();
        if (!empty($this->enquiry->ingested_at)) {
            return;
        }
        if ($this->process()) {
            $this->enquiry->refresh()->update([
                'ingested_at' => Carbon::now(),
                'customer_id' => $this->getCustomerId($this->enquiry->email, $this->getEnquiryDetails()),
            ]);
        }
    }

    private function process(): bool
    {
        // No confirmation on partial submissions
        if ($this->enquiry->status_id > 0) {
            $this->sendConfirmation();
        }

        $actions = $this->enquiry
            ->actions()
            ->whereNull('processed_at')
            ->where('attempts', '<', config('crm.enquiry_action_max_attempts'))
            ->get()
            ->filter(fn (EnquiryActionModel $action) => $this->shouldFireAction($action));

        if ($actions->isEmpty()) {
            return true;
        }

        // Run every action independently so one failure does not short-circuit the rest of the chain.
        // ingested_at is only set when all actions in this run succeed; failures retry on the next ingest.
        return $actions
            ->map(fn (EnquiryActionModel $action) => $this->handleEnquiryAction($action))
            ->every(fn (bool $result) => $result);
    }

    /**
     * Resolve the handler for an action. UI-configurable lead exports use a synthetic
     * "lead-export:<id>" action name that is not registered in config('crm.form_actions'),
     * so they are resolved directly here; everything else goes through the package
     * registry as before.
     */
    private function resolveHandler(string $actionName): EnquiryAction
    {
        if (str_starts_with($actionName, LeadExport::PROVIDER_PREFIX)) {
            return app(SendLeadExportNotification::class);
        }

        return Enquiries::makeAction($actionName);
    }

    private function shouldFireAction(EnquiryActionModel $action): bool
    {
        $trigger = $action->data['trigger'] ?? 'all';

        return match ($trigger) {
            'partial' => $this->enquiry->status_id === 0,
            'full' => $this->enquiry->status_id > 0,
            default => true,
        };
    }

    private function handleEnquiryAction(EnquiryActionModel $action): bool
    {
        try {
            $handler = $this->resolveHandler($action->action_name);
            if ($handler->handle($action, $this->enquiry) === false) {
                $action->update([
                    'failed_at' => Carbon::now(),
                    'context' => $this->limitContext('Failed with reason: ' . $handler->failureReason()),
                ]);
                $action->increment('attempts');
                return false;
            }

            $action->update([
                'processed_at' => Carbon::now(),
                'failed_at' => null,
                'context' => $this->limitContext($handler->successDetails()),
            ]);
        } catch (Exception $exception) {
            Log::error('Enquiry action failed: ' . $exception->getMessage(), [
                'enquiry' => $this->enquiry,
                'action' => $action,
                'error' => $exception,
            ]);
            $action->update([
                'failed_at' => Carbon::now(),
                'context' => $this->limitContext('Failed due to a system error: ' . $exception->getMessage()),
            ]);
            $action->increment('attempts');
            return false;
        }

        return true;
    }

    private function limitContext(string $context): string
    {
        return Str::limit($context, self::CONTEXT_MAX_LENGTH - 3);
    }

    private function sendConfirmation(): void
    {
        if ($this->enquiry->confirmation_sent) {
            return;
        }

        if (empty($this->enquiry->form->send_customer_confirmation_email)) {
            $this->enquiry->refresh()->update([
                'confirmation_sent' => true,
            ]);
            return;
        }

        try {
            $recipient = $this->enquiry->email;
            if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
                return;
            }
            $class = config('crm.enquiry_confirmation_mail');
            Mail::to($this->enquiry->email)->send(new $class($this->enquiry));
            $this->enquiry->refresh()->update([
                'confirmation_sent' => true,
            ]);
        } catch (\Exception $exception) {
            Log::warning($exception->getMessage());
        }
    }

    private function getEnquiryDetails(): array
    {
        $details = [];
        foreach ($this->enquiry->getNormalizedAnswers() as $answer) {
            $is_phone_number = stripos((string) $answer['question'], 'Contact Number') !== false
                || stripos((string) $answer['question'], 'Contact No') !== false
                || stripos((string) $answer['question'], 'Contact Number') !== false;
            if ($is_phone_number) {
                $details['phone_number'] = $answer['answer'];
            }

            $is_first_name = stripos((string) $answer['question'], 'First Name') !== false
                || stripos((string) $answer['question'], 'Firstname') !== false;
            if ($is_first_name) {
                $details['first_name'] = $answer['answer'];
            }

            $is_last_name = stripos((string) $answer['question'], 'Last Name') !== false
                || stripos((string) $answer['question'], 'Lastname') !== false
                || stripos((string) $answer['question'], 'Surname') !== false;
            if ($is_last_name) {
                $details['last_name'] = $answer['answer'];
            }
        }

        return $details;
    }
}
