<?php

namespace App\Services\Resume;

use App\Contracts\ResumeTextExtractor;
use App\Models\ResumeReport;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use Symfony\Component\Process\Process;
use ZipArchive;

class LocalResumeTextExtractor implements ResumeTextExtractor
{
    public function extract(ResumeReport $resumeReport): string
    {
        $disk = Storage::disk($resumeReport->disk);

        if (! $disk->exists($resumeReport->path)) {
            throw new RuntimeException('Resume file was not found in private storage.');
        }

        $extension = strtolower(pathinfo($resumeReport->original_filename ?: $resumeReport->path, PATHINFO_EXTENSION));
        $mimeType = strtolower($resumeReport->mime_type);

        if ($mimeType === 'text/plain' || $extension === 'txt') {
            return $this->normalize((string) $disk->get($resumeReport->path));
        }

        $localPath = $disk->path($resumeReport->path);

        return match (true) {
            $extension === 'docx' || str_contains($mimeType, 'wordprocessingml') => $this->extractDocx($localPath),
            $extension === 'pdf' || $mimeType === 'application/pdf' => $this->extractPdf($localPath),
            $extension === 'doc' || in_array($mimeType, [
                'application/msword',
                'application/x-ole-storage',
            ], true) => $this->extractDoc($localPath),
            default => throw new RuntimeException('Unsupported resume format. Upload PDF, DOC, DOCX, or TXT.'),
        };
    }

    private function extractDocx(string $path): string
    {
        if (! class_exists(ZipArchive::class)) {
            throw new RuntimeException('DOCX text extraction requires the PHP Zip extension.');
        }

        $zip = new ZipArchive;

        if ($zip->open($path) !== true) {
            throw new RuntimeException('DOCX file could not be opened for text extraction.');
        }

        $text = '';

        try {
            for ($index = 0; $index < $zip->numFiles; $index++) {
                $name = (string) $zip->getNameIndex($index);

                if (! str_starts_with($name, 'word/') || ! str_ends_with($name, '.xml')) {
                    continue;
                }

                if (! str_contains($name, 'document') && ! str_contains($name, 'header') && ! str_contains($name, 'footer')) {
                    continue;
                }

                $xml = $zip->getFromIndex($index);

                if (! is_string($xml)) {
                    continue;
                }

                $xml = preg_replace('/<w:tab\s*\/?>/i', ' ', $xml) ?? $xml;
                $xml = preg_replace('/<\/w:(p|tr)>/i', "\n", $xml) ?? $xml;
                $text .= ' '.strip_tags($xml);
            }
        } finally {
            $zip->close();
        }

        $text = html_entity_decode($text, ENT_QUOTES | ENT_XML1, 'UTF-8');
        $text = $this->normalize($text);

        if ($text === '') {
            throw new RuntimeException('DOCX text could not be extracted.');
        }

        return $text;
    }

    private function extractPdf(string $path): string
    {
        return $this->runExtractor(
            [
                (string) config('resume.extractors.pdftotext_binary', 'pdftotext'),
                '-layout',
                $path,
                '-',
            ],
            'PDF text extraction failed. Install Poppler pdftotext or configure RESUME_PDFTOTEXT_BINARY.'
        );
    }

    private function extractDoc(string $path): string
    {
        foreach ([
            [(string) config('resume.extractors.antiword_binary', 'antiword'), $path],
            [(string) config('resume.extractors.catdoc_binary', 'catdoc'), $path],
        ] as $command) {
            try {
                return $this->runExtractor(
                    $command,
                    'Legacy DOC text extraction failed. Install antiword or catdoc, or upload DOCX/TXT.'
                );
            } catch (RuntimeException) {
                continue;
            }
        }

        throw new RuntimeException('Legacy DOC text extraction failed. Install antiword or catdoc, or upload DOCX/TXT.');
    }

    /**
     * @param  array<int, string>  $command
     */
    private function runExtractor(array $command, string $failureMessage): string
    {
        $process = new Process($command);
        $process->setTimeout((int) config('resume.extractors.timeout', 20));
        $process->run();

        if (! $process->isSuccessful()) {
            throw new RuntimeException($failureMessage);
        }

        $text = $this->normalize($process->getOutput());

        if ($text === '') {
            throw new RuntimeException($failureMessage);
        }

        return $text;
    }

    private function normalize(string $text): string
    {
        $text = preg_replace('/[ \t]+/', ' ', $text) ?? $text;
        $text = preg_replace('/\R{3,}/', "\n\n", $text) ?? $text;

        return trim($text);
    }
}
