<?php

namespace App\Jobs\Synapse;

use App\Models\Contact\Hub\ContactMessage;
use App\Models\Contact\Hub\ContactMessageAttachment;
use App\Models\Dsar\DsarEvidenceItem;
use App\Models\Synapse\SynapseRun;
use App\Synapse\Agents\ExtractDsarEvidenceAgent;
use App\Synapse\Agents\FilterDsarPagesAgent;
use App\Synapse\Agents\VerifyDsarDocumentAgent;
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 Laravel\Ai\Exceptions\RateLimitedException;
use Laravel\Ai\Files;
use Smalot\PdfParser\Parser as PdfParser;

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

    public int $tries   = 3;
    public int $timeout = 600;
    public int $backoff = 60;

    /** Documents above this page count skip the AI verify/filter steps and use text chunking. */
    private const LARGE_DOC_THRESHOLD = 40;

    /** Max pages of text sent to the extraction agent per call. */
    private const CHUNK_SIZE = 25;

    /** Pages whose extracted text is shorter than this are treated as image/blank (filter heuristic). */
    private const MIN_TEXT_CHARS = 80;

    public function __construct(
        public readonly int  $attachmentId,
        public readonly ?int $dsarId = null,
    ) {}

    public function handle(): void
    {
        $attachment = ContactMessageAttachment::find($this->attachmentId);

        if (!$attachment) {
            Log::warning("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} not found.");
            return;
        }

        $attachment->update(['evidence_status' => 'processing']);

        // Resolve solicitor_id from the parent message — app(Solicitor::class) is null in queue context.
        $solicitorId = ContactMessage::where('id', $attachment->contact_message_id)->value('solicitor_id');

        try {
            $isPdf = str_contains($attachment->mime ?? '', 'pdf');

            // Parse per-page text for PDFs so we know the page count and can chunk large docs.
            $perPageText = $isPdf ? $this->parsePageText($attachment) : [];
            $pageCount   = count($perPageText);
            $isLarge     = $pageCount >= self::LARGE_DOC_THRESHOLD;

            Log::info("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} — pages={$pageCount}, large=" . ($isLarge ? 'yes' : 'no') . ".");

            // ── Stage 1: Verify ───────────────────────────────────────────────
            // Large PDFs are almost certainly real data records; skip the AI verify step.
            if ($isLarge) {
                $attachment->update(['evidence_is_dsar_doc' => true]);
            } else {
                $file       = $this->loadFile($attachment);
                $verify     = (new VerifyDsarDocumentAgent)->prompt(
                    "Filename: {$attachment->filename}\nMIME type: {$attachment->mime}",
                    attachments: [$file]
                );
                $verifyData = $verify->structured;

                Log::info("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} — is_dsar_document={$verifyData['is_dsar_document']}, confidence={$verifyData['confidence']}.");

                if (!$verifyData['is_dsar_document'] && in_array($verifyData['confidence'], ['high', 'medium'], true)) {
                    $attachment->update([
                        'evidence_status'      => 'skipped',
                        'evidence_is_dsar_doc' => false,
                    ]);
                    return;
                }

                $attachment->update(['evidence_is_dsar_doc' => true]);
            }

            // ── Stage 2: Filter pages ─────────────────────────────────────────
            if ($isLarge) {
                // Heuristic filter: skip pages with very little text (images, ECGs, blanks).
                $relevantPageNums = [];
                $excludedSummary  = [];

                foreach ($perPageText as $i => $text) {
                    $pageNum = $i + 1; // 1-indexed
                    if (mb_strlen(trim($text)) >= self::MIN_TEXT_CHARS) {
                        $relevantPageNums[] = $pageNum;
                    } else {
                        $excludedSummary[] = $pageNum;
                    }
                }

                $filterRecord = [
                    'method'        => 'heuristic',
                    'total_pages'   => $pageCount,
                    'relevant'      => count($relevantPageNums),
                    'excluded'      => count($excludedSummary),
                    'excluded_pages' => $excludedSummary,
                ];
                $this->recordSynapseRun('dsar_page_filter', $attachment, $filterRecord, $solicitorId);

                Log::info("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} — heuristic filter: {$filterRecord['relevant']} relevant, {$filterRecord['excluded']} excluded.");
            } else {
                $file       = $file ?? $this->loadFile($attachment);
                $filter     = (new FilterDsarPagesAgent)->prompt(
                    "Filename: {$attachment->filename}",
                    attachments: [$file]
                );
                $filterData = $filter->structured;

                $this->recordSynapseRun('dsar_page_filter', $attachment, $filterData, $solicitorId);

                $relevantPageNums = array_values(array_filter(
                    array_map('intval', explode(',', $filterData['relevant_pages_csv'] ?? '')),
                    fn ($n) => $n > 0
                ));

                Log::info("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} — AI filter: " . count($relevantPageNums) . " relevant of {$filterData['total_pages']}.");
            }

            if (empty($relevantPageNums)) {
                $attachment->update(['evidence_status' => 'skipped']);
                return;
            }

            // ── Stage 3: Extract evidence ─────────────────────────────────────
            $totalItems = 0;

            if ($isLarge) {
                // Chunk relevant pages and extract text-only (no file attachment).
                $chunks = array_chunk($relevantPageNums, self::CHUNK_SIZE);

                foreach ($chunks as $chunkIndex => $chunkPages) {
                    $chunkText = $this->buildChunkText($chunkPages, $perPageText);

                    $prompt = "Filename: {$attachment->filename} (chunk " . ($chunkIndex + 1) . " of " . count($chunks) . ")\n"
                        . "Pages in this chunk: " . implode(', ', $chunkPages) . "\n\n"
                        . $chunkText;

                    $extract     = (new ExtractDsarEvidenceAgent)->prompt($prompt);
                    $extractData = $extract->structured;
                    $items       = json_decode($extractData['items_json'] ?? '[]', true) ?? [];

                    $totalItems += $this->saveItems($items, $attachment->id, $solicitorId);

                    Log::info("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} — chunk {$chunkIndex}/{$chunkIndex} extracted " . count($items) . " items.");
                }
            } else {
                // Small doc: send the PDF file directly for visual context.
                $file          = $file ?? $this->loadFile($attachment);
                $relevantPages = implode(', ', $relevantPageNums);
                $excludedNotes = trim($filterData['exclusion_notes'] ?? '');

                $prompt = "Filename: {$attachment->filename}\n"
                    . "Focus your analysis on pages: {$relevantPages}.\n"
                    . ($excludedNotes ? "Skip these diagnostic-imagery pages: {$excludedNotes}.\n" : '')
                    . ($attachment->extracted_text
                        ? "\nExtracted text for reference:\n" . substr($attachment->extracted_text, 0, 40000)
                        : '');

                $extract     = (new ExtractDsarEvidenceAgent)->prompt($prompt, attachments: [$file]);
                $extractData = $extract->structured;
                $items       = json_decode($extractData['items_json'] ?? '[]', true) ?? [];

                $totalItems += $this->saveItems($items, $attachment->id, $solicitorId);
            }

            $this->recordSynapseRun('dsar_evidence_extractor', $attachment, ['item_count' => $totalItems], $solicitorId);

            $attachment->update(['evidence_status' => 'completed']);

            Log::info("ExtractEvidenceFromAttachmentJob: attachment {$this->attachmentId} — done, {$totalItems} total evidence items.");

        } catch (RateLimitedException $e) {
            // Back off exponentially and re-queue — do not consume a retry or mark as failed.
            $delay = min(60 * $this->attempts(), 600);
            Log::warning("ExtractEvidenceFromAttachmentJob: rate limited for attachment {$this->attachmentId}, releasing in {$delay}s.");
            $this->release($delay);
        } catch (\Throwable $e) {
            $attachment->update(['evidence_status' => 'failed']);

            Log::error("ExtractEvidenceFromAttachmentJob: failed for attachment {$this->attachmentId}.", [
                'error' => $e->getMessage(),
            ]);

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

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

    /**
     * Extract text from each page of a PDF. Returns array indexed 0..N-1.
     * Returns [] for non-PDFs or if parsing fails.
     */
    private function parsePageText(ContactMessageAttachment $attachment): array
    {
        try {
            $contents = $attachment->contents();
            $parser   = new PdfParser();
            $pdf      = $parser->parseContent($contents);
            $pages    = $pdf->getPages();

            return array_map(fn ($page) => $page->getText(), $pages);
        } catch (\Throwable $e) {
            Log::warning("ExtractEvidenceFromAttachmentJob: per-page parse failed for attachment {$this->attachmentId}: {$e->getMessage()}");
            return [];
        }
    }

    /**
     * Build a text block for a set of page numbers from the per-page text array.
     */
    private function buildChunkText(array $pageNums, array $perPageText): string
    {
        $parts = [];
        foreach ($pageNums as $pageNum) {
            $text = $perPageText[$pageNum - 1] ?? '';
            if (trim($text) !== '') {
                $parts[] = "=== PAGE {$pageNum} ===\n" . trim($text);
            }
        }
        return implode("\n\n", $parts);
    }

    /**
     * Persist a batch of evidence items returned by the agent.
     * Returns count of items saved.
     */
    private function saveItems(array $items, int $attachmentId, ?int $solicitorId): int
    {
        $count = 0;
        foreach ($items as $item) {
            if (empty($item['quote']) || empty($item['category'])) {
                continue;
            }
            DsarEvidenceItem::create([
                'solicitor_id'                  => $solicitorId,
                'contact_message_attachment_id' => $attachmentId,
                'dsar_id'                        => $this->dsarId,
                'page'                           => ($item['page'] ?? 0) ?: null,
                'quote'                          => $item['quote'],
                'category'                       => $item['category'],
                'severity'                       => $item['severity'] ?? 'medium',
                'comment'                        => $item['comment'] ?? '',
                'source'                         => 'ai',
            ]);
            $count++;
        }
        return $count;
    }

    private function loadFile(ContactMessageAttachment $attachment): Files\Document|Files\Image
    {
        $isImage = str_starts_with($attachment->mime ?? '', 'image/');

        return $isImage
            ? Files\Image::fromStorage($attachment->path, disk: $attachment->disk)
            : Files\Document::fromStorage($attachment->path, disk: $attachment->disk);
    }

    private function recordSynapseRun(string $agentType, ContactMessageAttachment $attachment, array $result, ?int $solicitorId = null): void
    {
        if (!config('synapse.record_runs', true)) {
            return;
        }

        SynapseRun::create([
            'solicitor_id' => $solicitorId,
            'agent_type'   => $agentType,
            'subject_type' => ContactMessageAttachment::class,
            'subject_id'   => $attachment->id,
            'status'       => 'completed',
            'result'       => $result,
        ]);
    }
}
