<?php

namespace App\Jobs\Contact;

use App\Jobs\Synapse\ClassifyInboundEmailJob;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\Contact\Hub\ContactMessageAttachment;
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\Storage;
use Smalot\PdfParser\Parser as PdfParser;
use ZipArchive;

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

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

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

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

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

        foreach ($message->attachments as $attachment) {
            $this->extractAttachment($attachment);
        }

        ClassifyInboundEmailJob::dispatch($this->messageId);
    }

    private function extractAttachment(ContactMessageAttachment $attachment): void
    {
        $attachment->update(['extraction_status' => 'processing']);

        try {
            $text = $this->extractText($attachment);

            if ($text !== null) {
                $attachment->update([
                    'extracted_text'   => mb_substr(trim($text), 0, 100000),
                    'extraction_status' => 'completed',
                ]);
                Log::info("ExtractMessageAttachmentsJob: extracted text from attachment {$attachment->id} ({$attachment->filename}).");
            } else {
                $attachment->update(['extraction_status' => 'skipped']);
                Log::info("ExtractMessageAttachmentsJob: attachment {$attachment->id} ({$attachment->filename}) skipped — unsupported type.");
            }
        } catch (\Throwable $e) {
            $attachment->update(['extraction_status' => 'failed']);
            Log::warning("ExtractMessageAttachmentsJob: failed to extract attachment {$attachment->id} ({$attachment->filename}).", [
                'error' => $e->getMessage(),
            ]);
        }
    }

    private function extractText(ContactMessageAttachment $attachment): ?string
    {
        $mime     = strtolower($attachment->mime ?? '');
        $filename = strtolower($attachment->filename ?? '');
        $path     = Storage::disk($attachment->disk)->path($attachment->path);

        if (!file_exists($path)) {
            throw new \RuntimeException("File not found on disk: {$path}");
        }

        // PDF
        if (str_contains($mime, 'pdf') || str_ends_with($filename, '.pdf')) {
            return $this->extractPdf($path);
        }

        // DOCX (Office Open XML) — readable without an extra package
        if (str_contains($mime, 'officedocument.wordprocessingml')
            || str_contains($mime, 'openxmlformats')
            || str_ends_with($filename, '.docx')) {
            return $this->extractDocx($path);
        }

        // Plain text — just read it directly
        if (str_starts_with($mime, 'text/')) {
            return Storage::disk($attachment->disk)->get($attachment->path);
        }

        // Everything else (images, legacy .doc, spreadsheets, etc.) — manual fallback
        return null;
    }

    private function extractPdf(string $path): string
    {
        $parser = new PdfParser();
        $pdf    = $parser->parseFile($path);
        return $pdf->getText();
    }

    private function extractDocx(string $path): string
    {
        $zip = new ZipArchive();

        if ($zip->open($path) !== true) {
            throw new \RuntimeException('Cannot open DOCX file as ZIP archive.');
        }

        $xml = $zip->getFromName('word/document.xml');
        $zip->close();

        if ($xml === false) {
            return '';
        }

        // Paragraph breaks → newlines, then strip all remaining XML tags
        $text = preg_replace('/<\/w:p>/', "\n", $xml);
        $text = strip_tags($text);

        // Collapse excess whitespace while preserving paragraph breaks
        return preg_replace('/\n{3,}/', "\n\n", trim($text));
    }
}
