<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;

final class DocumentStore
{
    public const MAX_BYTES = 8388608;

    public static function root(): string
    {
        return str_replace('\\', '/', dirname(BASE_PATH)) . '/storage/docs';
    }

    public static function path(int $tenantId, int $documentId, bool $signed = false): string
    {
        $suffix = $signed ? '-signed' : '';
        return self::root() . '/' . $tenantId . '/' . $documentId . $suffix . '.pdf';
    }

    /** @param array<string, mixed> $file */
    public static function accept(array $file, string $empty = 'Choose a PDF to share.'): string
    {
        $err = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($err === UPLOAD_ERR_NO_FILE) {
            throw new \RuntimeException($empty);
        }
        if ($err !== UPLOAD_ERR_OK) {
            throw new \RuntimeException('The PDF did not upload. Try a smaller file.');
        }
        $tmp = (string) ($file['tmp_name'] ?? '');
        $name = self::safeName((string) ($file['name'] ?? 'document.pdf'));
        $size = (int) ($file['size'] ?? 0);
        if ($tmp === '' || !is_uploaded_file($tmp)) {
            throw new \RuntimeException('The PDF did not upload.');
        }
        if ($size < 5 || $size > self::MAX_BYTES) {
            throw new \RuntimeException('Use a PDF up to 8 MB.');
        }
        $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        if ($ext !== 'pdf') {
            throw new \RuntimeException('Only PDF files can be shared.');
        }
        $mime = '';
        if (class_exists(\finfo::class)) {
            $mime = (string) (new \finfo(FILEINFO_MIME_TYPE))->file($tmp);
        }
        $head = (string) file_get_contents($tmp, false, null, 0, 5);
        if ($head !== '%PDF-' || ($mime !== '' && $mime !== 'application/pdf' && $mime !== 'application/x-pdf')) {
            throw new \RuntimeException('That file is not a PDF.');
        }
        return $name;
    }

    public static function entityPath(int $tenantId, string $entity, int $id): string
    {
        if (!in_array($entity, ['quote', 'contract', 'invoice'], true)) {
            throw new \RuntimeException('Invalid file type.');
        }
        return self::root() . '/' . $tenantId . '/' . $entity . '-' . $id . '.pdf';
    }

    public static function save(int $documentId, string $tmpPath, bool $signed = false): void
    {
        $dest = self::path((int) Auth::instance()->tenantId(), $documentId, $signed);
        self::copyInto($dest, $tmpPath);
    }

    public static function saveEntity(string $entity, int $id, string $tmpPath): void
    {
        $dest = self::entityPath((int) Auth::instance()->tenantId(), $entity, $id);
        self::copyInto($dest, $tmpPath);
    }

    public static function copyToDocument(int $documentId, string $sourcePath): void
    {
        $dest = self::path((int) Auth::instance()->tenantId(), $documentId);
        self::copyInto($dest, $sourcePath);
    }

    public static function existsEntity(int $tenantId, string $entity, int $id): bool
    {
        return is_file(self::entityPath($tenantId, $entity, $id));
    }

    public static function streamEntity(int $tenantId, string $entity, int $id, string $downloadName): void
    {
        self::streamFile(self::entityPath($tenantId, $entity, $id), $downloadName);
    }

    private static function copyInto(string $dest, string $sourcePath): void
    {
        $dir = dirname($dest);
        if (!is_dir($dir) && !mkdir($dir, 0750, true) && !is_dir($dir)) {
            throw new \RuntimeException('Could not store the PDF.');
        }
        if (!is_file($sourcePath) || !@copy($sourcePath, $dest) || !is_file($dest)) {
            throw new \RuntimeException('Could not store the PDF.');
        }
    }

    public static function exists(int $tenantId, int $documentId, bool $signed = false): bool
    {
        return is_file(self::path($tenantId, $documentId, $signed));
    }

    public static function stream(int $tenantId, int $documentId, string $downloadName, bool $signed = false): void
    {
        self::streamFile(self::path($tenantId, $documentId, $signed), $downloadName);
    }

    private static function streamFile(string $path, string $downloadName): void
    {
        if (!is_file($path)) {
            http_response_code(404);
            echo 'File not found.';
            return;
        }
        $name = self::safeName($downloadName !== '' ? $downloadName : 'document.pdf');
        header('Content-Type: application/pdf');
        header('Content-Disposition: inline; filename="' . $name . '"');
        header('X-Content-Type-Options: nosniff');
        header('Content-Length: ' . (string) filesize($path));
        readfile($path);
    }

    public static function safeName(string $name): string
    {
        $base = basename(str_replace(["\0", '\\'], '', $name));
        $base = preg_replace('/[^A-Za-z0-9._-]+/', '-', $base) ?? 'document.pdf';
        $base = trim($base, '.-');
        if ($base === '' || !str_ends_with(strtolower($base), '.pdf')) {
            $base = ($base !== '' ? $base : 'document') . '.pdf';
        }
        return mb_substr($base, 0, 160);
    }

    /** @var array<string, list<string>> */
    public const ATTACHMENT_MIMES = [
        'pdf' => ['application/pdf', 'application/x-pdf'],
        'doc' => ['application/msword', 'application/octet-stream'],
        'docx' => [
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/zip',
            'application/octet-stream',
        ],
        'jpg' => ['image/jpeg', 'image/pjpeg'],
        'jpeg' => ['image/jpeg', 'image/pjpeg'],
        'png' => ['image/png', 'image/x-png'],
    ];

    /** @param array<string, mixed> $file */
    public static function acceptAttachment(array $file): string
    {
        $err = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($err === UPLOAD_ERR_NO_FILE) {
            throw new \RuntimeException('Choose a PDF, Word file, JPEG, or PNG.');
        }
        if ($err !== UPLOAD_ERR_OK) {
            throw new \RuntimeException('The file did not upload. Try a smaller file.');
        }
        $tmp = (string) ($file['tmp_name'] ?? '');
        $name = self::safeAttachmentName((string) ($file['name'] ?? 'file'));
        $size = (int) ($file['size'] ?? 0);
        if ($tmp === '' || !is_uploaded_file($tmp)) {
            throw new \RuntimeException('The file did not upload.');
        }
        if ($size < 5 || $size > self::MAX_BYTES) {
            throw new \RuntimeException('Use a file up to 8 MB.');
        }
        $ext = strtolower((string) pathinfo($name, PATHINFO_EXTENSION));
        if (!isset(self::ATTACHMENT_MIMES[$ext])) {
            throw new \RuntimeException('Use a PDF, Word (.doc, .docx), JPEG, or PNG.');
        }
        $mime = '';
        if (class_exists(\finfo::class)) {
            $mime = (string) (new \finfo(FILEINFO_MIME_TYPE))->file($tmp);
        }
        $head = (string) file_get_contents($tmp, false, null, 0, 8);
        $ok = match ($ext) {
            'pdf' => str_starts_with($head, '%PDF-'),
            'jpg', 'jpeg' => str_starts_with($head, "\xFF\xD8\xFF"),
            'png' => str_starts_with($head, "\x89PNG\r\n\x1a\n"),
            'doc' => str_starts_with($head, "\xD0\xCF\x11\xE0"),
            'docx' => str_starts_with($head, "PK\x03\x04"),
            default => false,
        };
        if (!$ok) {
            throw new \RuntimeException('That file type does not match the name.');
        }
        $allowed = self::ATTACHMENT_MIMES[$ext];
        if ($mime !== '' && !in_array($mime, $allowed, true)) {
            throw new \RuntimeException('Use a PDF, Word (.doc, .docx), JPEG, or PNG.');
        }
        return $name;
    }

    public static function attachmentPath(int $tenantId, string $kind, int $id, string $fileName): string
    {
        if (!in_array($kind, ['task', 'activity'], true)) {
            throw new \RuntimeException('Invalid file type.');
        }
        $ext = strtolower((string) pathinfo(self::safeAttachmentName($fileName), PATHINFO_EXTENSION));
        if (!isset(self::ATTACHMENT_MIMES[$ext])) {
            throw new \RuntimeException('Invalid file type.');
        }
        return self::root() . '/' . $tenantId . '/' . $kind . '-' . $id . '.' . $ext;
    }

    public static function saveAttachment(string $kind, int $id, string $tmpPath, string $fileName): void
    {
        $dest = self::attachmentPath((int) Auth::instance()->tenantId(), $kind, $id, $fileName);
        self::copyInto($dest, $tmpPath);
    }

    public static function existsAttachment(int $tenantId, string $kind, int $id, string $fileName): bool
    {
        return is_file(self::attachmentPath($tenantId, $kind, $id, $fileName));
    }

    public static function streamAttachment(int $tenantId, string $kind, int $id, string $downloadName): void
    {
        $path = self::attachmentPath($tenantId, $kind, $id, $downloadName);
        if (!is_file($path)) {
            http_response_code(404);
            echo 'File not found.';
            return;
        }
        $name = self::safeAttachmentName($downloadName !== '' ? $downloadName : 'file');
        $ext = strtolower((string) pathinfo($name, PATHINFO_EXTENSION));
        $mime = match ($ext) {
            'pdf' => 'application/pdf',
            'doc' => 'application/msword',
            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'jpg', 'jpeg' => 'image/jpeg',
            'png' => 'image/png',
            default => 'application/octet-stream',
        };
        $disp = in_array($ext, ['pdf', 'jpg', 'jpeg', 'png'], true) ? 'inline' : 'attachment';
        header('Content-Type: ' . $mime);
        header('Content-Disposition: ' . $disp . '; filename="' . $name . '"');
        header('X-Content-Type-Options: nosniff');
        header('Content-Length: ' . (string) filesize($path));
        readfile($path);
    }

    public static function safeAttachmentName(string $name): string
    {
        $base = basename(str_replace(["\0", '\\'], '', $name));
        $base = preg_replace('/[^A-Za-z0-9._-]+/', '-', $base) ?? 'file';
        $base = trim($base, '.-');
        $ext = strtolower((string) pathinfo($base, PATHINFO_EXTENSION));
        if ($base === '' || !isset(self::ATTACHMENT_MIMES[$ext])) {
            throw new \RuntimeException('Use a PDF, Word (.doc, .docx), JPEG, or PNG.');
        }
        return mb_substr($base, 0, 160);
    }
}
