<?php

namespace App\Jobs\Synapse;

use App\Enum\Dsar\DsarStatus;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\Dsar\Dsar;
use App\Services\HistoryService;
use App\Synapse\Agents\ClassifyDsarResponseAgent;
use App\Synapse\Agents\DsarMatcherAgent;
use App\Synapse\Agents\ExtractDsarReferenceAgent;
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\DB;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\Exceptions\RateLimitedException;

class FindRelatedDsarJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries   = 2;
    public int $timeout = 120;
    public int $backoff = 30;

    public function __construct(
        public readonly int $messageId,
    ) {}

    public function handle(): void
    {
        $message = ContactMessage::find($this->messageId);

        if (!$message) {
            Log::warning("FindRelatedDsarJob: message {$this->messageId} not found.");
            return;
        }

        try {
            $prompt = $message->aiPrompt();
            $dsar   = null;

            // ── Stage 0: regex-match our own reference (free, instant) ────────
            $ourRef = $this->regexExtractOurReference($message);

            if ($ourRef) {
                $dsar = Dsar::withoutGlobalScopes()
                    ->where('reference', $ourRef)
                    ->first();

                if ($dsar) {
                    $message->forceFill([
                        'messageable_type' => Dsar::class,
                        'messageable_id'   => $dsar->id,
                    ])->save();

                    Log::info("FindRelatedDsarJob: message {$this->messageId} matched via regex reference '{$ourRef}' → DSAR {$dsar->id}.");
                } else {
                    Log::info("FindRelatedDsarJob: message {$this->messageId} regex found '{$ourRef}' but no DSAR matches.");
                }
            }

            // ── Stages 1–2: AI reference extraction + matcher (skip if regex matched) ──
            if (!$dsar) {
                // Stage 1: extract references from the email
                $refResult = $this->extractReferences($prompt);
                $dsarRef   = trim($refResult['dsar_reference'] ?? '');
                $instRef   = trim($refResult['inst_reference'] ?? '');

                Log::info("FindRelatedDsarJob: message {$this->messageId} — dsar_ref='{$dsarRef}', inst_ref='{$instRef}'.");

                // Stage 2: Match DSAR by reference (deterministic)
                $allRefs = array_values(array_filter([$dsarRef, $instRef], fn ($r) => $r !== ''));

                if (!empty($allRefs)) {
                    $dsar = Dsar::withoutGlobalScopes()
                        ->where(function ($q) use ($allRefs) {
                            $q->whereIn('reference', $allRefs)
                              ->orWhereIn('receiver_reference', $allRefs);
                        })
                        ->first();

                    if ($dsar) {
                        Log::info("FindRelatedDsarJob: message {$this->messageId} matched DSAR {$dsar->id} via reference lookup (" . implode(', ', $allRefs) . ").");

                        $message->forceFill([
                            'messageable_type' => Dsar::class,
                            'messageable_id'   => $dsar->id,
                        ])->save();
                    }
                }

                // Stage 3: AI matcher fallback
                if (!$dsar) {
                    $matcherPrompt = $this->augmentPromptWithReferences($prompt, $refResult);
                    $match         = (new DsarMatcherAgent)->prompt($matcherPrompt);
                    $dsarId        = ($match['dsar_id'] ?? null) ?: null;
                    $confidence    = $match['confidence'] ?? 'low';

                    Log::info("FindRelatedDsarJob: message {$this->messageId} — AI matcher dsar_id={$dsarId}, confidence={$confidence}.");

                    if ($dsarId && in_array($confidence, ['high', 'medium'], true)) {
                        $dsar = Dsar::withoutGlobalScopes()->find($dsarId);

                        if ($dsar) {
                            $message->forceFill([
                                'messageable_type' => Dsar::class,
                                'messageable_id'   => $dsar->id,
                            ])->save();

                            Log::info("FindRelatedDsarJob: message {$this->messageId} linked to DSAR {$dsar->id}.");
                        }
                    }
                }

                // Stage 4: Save institution reference to DSAR if found
                $this->saveReceiverReference($refResult, $dsar);
            }

            // ── Stage 5: Classify what the institution said ───────────────────
            $classification = (new ClassifyDsarResponseAgent)->prompt($prompt);
            $responseType   = $classification['response_type'] ?? 'other';

            Log::info("FindRelatedDsarJob: message {$this->messageId} response_type={$responseType}.");

            // ── Stage 6: Route to triage queue ────────────────────────────────
            $queue = $this->resolveQueue($responseType);

            $message->forceFill([
                'triage_queue'  => $queue,
                'triage_status' => 'pending',
            ])->save();

            Log::info("FindRelatedDsarJob: message {$this->messageId} → triage_queue={$queue} (response_type={$responseType}, dsar_id=" . ($dsar?->id ?? 'none') . ").");

            if ($queue === 'dsar_evidence') {
                $message->load('attachments');
                foreach ($message->attachments as $attachment) {
                    ExtractEvidenceFromAttachmentJob::dispatch($attachment->id, $dsar?->id);
                }
            }

            // ── Stage 7: auto-advance the matched DSAR's status from the classification ──
            if ($dsar) {
                $newStatus = $this->resolveStatus($responseType);

                if ($newStatus) {
                    $payload = ['status' => $newStatus];

                    // A builder update bypasses the Dsar::booted() hook, so stamp
                    // received_at here. COALESCE keeps the original timestamp if the
                    // DSAR was reset out of a terminal status and re-completed.
                    if (in_array($newStatus, DsarStatus::terminal(), true)) {
                        $payload['received_at'] = DB::raw('COALESCE(received_at, NOW())');
                    }

                    // Guarded, atomic transition: the whereIn() ensures we never
                    // overwrite a terminal status or touch a not-yet-sent DSAR,
                    // regardless of what the in-memory $dsar->status holds.
                    $updated = Dsar::withoutGlobalScopes()
                        ->where('id', $dsar->id)
                        ->whereIn('status', DsarStatus::autoFromStates())
                        ->update($payload);

                    if ($updated) {
                        app(HistoryService::class)->add(
                            $dsar,
                            "DSAR status auto-updated to '{$newStatus}' from inbound classification ({$responseType}).",
                            [
                                'dsar_id'       => $dsar->id,
                                'message_id'    => $this->messageId,
                                'response_type' => $responseType,
                                'new_status'    => $newStatus,
                            ],
                        );

                        Log::info("FindRelatedDsarJob: message {$this->messageId} auto-set DSAR {$dsar->id} status → {$newStatus} (response_type={$responseType}).");
                    }
                }
            }

        } catch (RateLimitedException $e) {
            Log::warning("FindRelatedDsarJob: rate limited for message {$this->messageId}, releasing for retry in 90s.");
            $this->release(90);
        } catch (\Throwable $e) {
            Log::error("FindRelatedDsarJob: failed for message {$this->messageId}.", [
                'error' => $e->getMessage(),
            ]);

            report($e);
            $this->fail($e);
        }
    }

    // ── Helpers ──────────────────────────────────────────────────────────────

    /**
     * Scan the message (subject, body, attachments) for our own DSAR reference
     * pattern (DSAR-YYYYMMDD-XXXXXX) using a plain regex — no AI needed.
     */
    private function regexExtractOurReference(ContactMessage $message): ?string
    {
        $corpus = implode("\n", array_filter([
            $message->subject ?? '',
            $message->body_text ?? strip_tags($message->body_html ?? ''),
            ...$message->attachments->pluck('extracted_text')->filter()->all(),
        ]));

        if (preg_match('/DSAR-\d{8}-[A-Z0-9]{6}/i', $corpus, $matches)) {
            return strtoupper($matches[0]);
        }

        return null;
    }

    /**
     * Map response_type → triage_queue.
     *
     * dsar_evidence    — actual records received (data_provided, partial_data)
     * dsar_updates     — administrative responses needing no immediate action (acknowledgement, delay_notice)
     * dsar_needs_research — problematic responses requiring follow-up
     */
    private function resolveQueue(string $responseType): string
    {
        return match ($responseType) {
            'data_provided', 'partial_data'                                                            => 'dsar_evidence',
            'acknowledgement', 'delay_notice'                                                          => 'dsar_updates',
            'portal_response'                                                                          => 'portal_response',
            'info_requested', 'no_record', 'wrong_recipient', 'not_responsible', 'rejection', 'other'  => 'dsar_needs_research',
            default                                                                                    => 'dsar_needs_research',
        };
    }

    /**
     * Map response_type → DSAR status to auto-advance to, or null for "leave as-is".
     *
     * Only unambiguous outcomes auto-move. partial_data / wrong_recipient /
     * not_responsible / rejection / portal_response / other are deliberately left
     * for a human to disposition from the triage page.
     */
    private function resolveStatus(string $responseType): ?string
    {
        return match ($responseType) {
            'acknowledgement', 'delay_notice' => 'acknowledged',
            'data_provided'                   => 'completed',
            'info_requested'                  => 'further info required',
            'no_record'                       => 'no records',
            default                           => null,
        };
    }

    /**
     * Run the reference extractor and return its result.
     * Best-effort — returns an empty array on failure so the job continues.
     * Returns StructuredAgentResponse (ArrayAccess) on success, [] on failure.
     */
    private function extractReferences(string $prompt): mixed
    {
        try {
            return (new ExtractDsarReferenceAgent)->prompt($prompt);
        } catch (RateLimitedException $e) {
            throw $e; // propagate so the outer handler can release the job
        } catch (\Throwable $e) {
            Log::warning("FindRelatedDsarJob: reference extraction failed for message {$this->messageId}: {$e->getMessage()}");
            return [];
        }
    }

    /**
     * Append extracted reference numbers to the matcher prompt so the agent
     * can try a direct reference lookup before falling back to institution search.
     */
    private function augmentPromptWithReferences(string $prompt, mixed $refResult): string
    {
        $dsarRef = $refResult['dsar_reference'] ?? null;
        $instRef = $refResult['inst_reference'] ?? null;

        if (!$dsarRef && !$instRef) {
            return $prompt;
        }

        $lines = ["\n\nEXTRACTED REFERENCE NUMBERS:"];
        if ($dsarRef) {
            $lines[] = "- Firm's own reference (quoted back by institution): {$dsarRef}";
        }
        if ($instRef) {
            $lines[] = "- Institution's own reference: {$instRef}";
        }

        return $prompt . implode("\n", $lines);
    }

    /**
     * Save the institution's reference to the DSAR if found with sufficient
     * confidence and the field is currently blank.
     */
    private function saveReceiverReference(mixed $refResult, ?Dsar $dsar): void
    {
        $instRef = $refResult['inst_reference'] ?? null;
        $refConf = $refResult['confidence'] ?? 'low';

        if (!$instRef || !in_array($refConf, ['high', 'medium'], true)) {
            return;
        }

        if ($dsar) {
            Dsar::withoutGlobalScopes()
                ->where('id', $dsar->id)
                ->whereNull('receiver_reference')
                ->update(['receiver_reference' => $instRef]);

            Log::info("FindRelatedDsarJob: saved receiver_reference '{$instRef}' for DSAR {$dsar->id}.");
        } else {
            Log::info("FindRelatedDsarJob: message {$this->messageId} — extracted reference '{$instRef}' but no DSAR matched.");
        }
    }
}
