<?php

namespace App\Http\Controllers\Document;

use App\Http\Controllers\Controller;
use App\Models\Document\Document;
use App\Services\DocumentReviewWipService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\StreamedResponse;

class DocumentViewController extends Controller
{
    private const MIME_MAP = [
        'pdf'  => 'application/pdf',
        'doc'  => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'jpg'  => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png'  => 'image/png',
        'gif'  => 'image/gif',
        'webp' => 'image/webp',
        'bmp'  => 'image/bmp',
        'svg'  => 'image/svg+xml',
        'mp3'  => 'audio/mpeg',
        'wav'  => 'audio/wav',
        'eml'  => 'message/rfc822',
        'txt'  => 'text/plain',
        'csv'  => 'text/csv',
        'json' => 'application/json',
        'xml'  => 'application/xml',
        'zip'  => 'application/zip',
    ];

    /**
     * Stream the raw bytes of a document inline. Consumed by the preview viewers
     * (pdf.js, <img>, docx-preview) which fetch this same-origin with the session
     * cookie, so documents stay auth-gated and never leave the server.
     */
    public function raw(Request $request, Document $document): StreamedResponse
    {
        $this->authorise($request, $document);

        $path = $document->owner->getDocumentStoragePath() . $document->file_name;

        return Storage::disk('public')->response($path, $document->file_name, [
            'Content-Type'        => $this->mimeFor($document->file_type, $document->file_name),
            'Content-Disposition' => 'inline; filename="' . addslashes($document->file_name) . '"',
        ]);
    }

    public function download(Request $request, Document $document): StreamedResponse
    {
        $this->authorise($request, $document);

        $path = $document->owner->getDocumentStoragePath() . $document->file_name;

        return Storage::disk('public')->download($path, $document->file_name, [
            'Content-Type' => $this->mimeFor($document->file_type, $document->file_name),
        ]);
    }

    /**
     * Stream a single file from inside a zip document, inline. The viewer picks a
     * renderer (pdf.js / <img> / docx-preview / text) based on the inner extension.
     */
    public function zipEntry(Request $request, Document $document): Response
    {
        $this->authorise($request, $document);

        $entry = (string) $request->query('path', '');
        abort_if($entry === '' || str_contains($entry, "\0"), 400, 'Invalid entry.');

        $abs = Storage::disk('public')->path(
            $document->owner->getDocumentStoragePath() . $document->file_name
        );

        $zip = new \ZipArchive();
        abort_unless($zip->open($abs) === true, 404, 'Archive not found.');

        // statName confines us to entries that actually exist in the archive,
        // so there is no path-traversal surface here.
        if ($zip->statName($entry) === false) {
            $zip->close();
            abort(404, 'Entry not found.');
        }

        $contents = $zip->getFromName($entry);
        $zip->close();
        abort_if($contents === false, 404, 'Entry could not be read.');

        $ext  = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
        $mime = self::MIME_MAP[$ext] ?? 'application/octet-stream';

        return response($contents, 200, [
            'Content-Type'           => $mime,
            'Content-Disposition'    => 'inline; filename="' . addslashes(basename($entry)) . '"',
            'X-Content-Type-Options' => 'nosniff',
        ]);
    }

    /**
     * sendBeacon safety-net: books review WIP when the preview tab is closed
     * without using the "Finish" button. Deduped against the button by reviewToken.
     */
    public function bookReviewWip(Request $request, Document $document, DocumentReviewWipService $wip): Response
    {
        $this->authorise($request, $document);

        $wip->book(
            $document,
            (int) $request->input('seconds', 0),
            $request->filled('units') ? (int) $request->input('units') : null,
            $request->input('description'),
            (string) $request->input('reviewToken'),
            $request->input('startedAt'),
        );

        return response()->noContent();
    }

    private function mimeFor(?string $fileType, string $fileName): string
    {
        if ($fileType && Str::contains($fileType, '/')) {
            return $fileType;
        }

        $ext = $fileType ? strtolower($fileType) : strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

        return self::MIME_MAP[$ext] ?? 'application/octet-stream';
    }

    private function authorise(Request $request, Document $document): void
    {
        $path = $document->owner->getDocumentStoragePath() . $document->file_name;

        abort_unless(
            Storage::disk('public')->exists($path),
            404,
            'Document file not found.',
        );
    }
}
