<?php
/**
 * TenderSubmissionAttachments · Slice 8T.
 *
 * Proof-of-submission attachments per tender. Kinds cover the common evidence
 * types: courier waybill, portal screenshot, sealed-envelope photo, receipt,
 * acknowledgment email, other.
 *
 * File handling:
 *   - stores files under ROOT/uploads/tenders/{tenderId}/submissions/
 *   - MIME whitelist (docs + images + zip), 10 MB per file
 *   - filenames sanitized to uniqid + safe basename
 *   - soft-delete only (DB row flag) — files stay on disk until the tender is purged
 */
class TenderSubmissionAttachments extends Model
{
    public const KINDS = [
        'RECEIPT','COURIER_WAYBILL','PORTAL_SCREENSHOT','SEALED_ENVELOPE_PHOTO','ACKNOWLEDGMENT_EMAIL','OTHER',
    ];

    /** 10 MB per file. */
    public const MAX_BYTES = 10 * 1024 * 1024;

    /** Whitelisted MIME types. */
    public const ALLOWED_MIMES = [
        'application/pdf',
        'image/jpeg', 'image/png', 'image/webp', 'image/heic', 'image/gif',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'application/vnd.ms-excel',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'application/zip', 'application/x-zip-compressed',
        'text/plain',
    ];

    public function listFor(int $tenderId): array
    {
        $db = new DB();
        return $db->mySelect(
            "SELECT * FROM tender_submission_attachments
              WHERE tender_id = :t AND deleted = 0
              ORDER BY uploaded_on DESC, c0d DESC",
            [':t' => $tenderId]
        ) ?: [];
    }

    public function find(int $id): ?array
    {
        $db = new DB();
        $r = $db->mySelect("SELECT * FROM tender_submission_attachments WHERE c0d = :id AND deleted = 0", [':id' => $id]);
        return $r[0] ?? null;
    }

    /**
     * Upload one file. $file is a single $_FILES[...] entry.
     * Returns ['ok' => bool, 'id' => int, 'msg' => string?].
     */
    public function upload(int $tenderId, array $file, array $meta, int $userId, string $userName): array
    {
        if (empty($file) || !isset($file['tmp_name']) || $file['error'] !== UPLOAD_ERR_OK) {
            return ['ok' => false, 'msg' => 'No file received (upload error ' . ($file['error'] ?? '?') . ').'];
        }
        $size = (int)$file['size'];
        if ($size <= 0) return ['ok' => false, 'msg' => 'File is empty.'];
        if ($size > self::MAX_BYTES) return ['ok' => false, 'msg' => 'File too large (max ' . (self::MAX_BYTES / 1024 / 1024) . ' MB).'];

        // Probe MIME defensively (browsers lie).
        $probed = null;
        if (function_exists('finfo_open')) {
            $fi = finfo_open(FILEINFO_MIME_TYPE);
            if ($fi) { $probed = finfo_file($fi, $file['tmp_name']) ?: null; finfo_close($fi); }
        }
        $mime = $probed ?: (string)($file['type'] ?? '');
        if (!in_array($mime, self::ALLOWED_MIMES, true)) {
            return ['ok' => false, 'msg' => 'File type "' . htmlspecialchars($mime) . '" not allowed. Use PDF, image, DOC/DOCX, XLS/XLSX, or ZIP.'];
        }

        $kind = strtoupper((string)($meta['kind'] ?? 'OTHER'));
        if (!in_array($kind, self::KINDS, true)) $kind = 'OTHER';
        $label = trim((string)($meta['label'] ?? ''));
        if ($label === '') $label = basename((string)$file['name']);

        // Safe destination path.
        $origName = basename((string)$file['name']);
        $safe = preg_replace('/[^A-Za-z0-9._-]+/', '_', $origName);
        if (strlen($safe) > 120) $safe = substr($safe, -120);
        $dir  = ROOT . '/uploads/tenders/' . $tenderId . '/submissions';
        if (!is_dir($dir) && !@mkdir($dir, 0775, true)) {
            return ['ok' => false, 'msg' => 'Server could not create upload directory.'];
        }
        $fname = uniqid('sub', true) . '_' . $safe;
        $abs   = $dir . '/' . $fname;
        $rel   = 'uploads/tenders/' . $tenderId . '/submissions/' . $fname;
        if (!@move_uploaded_file($file['tmp_name'], $abs)) {
            return ['ok' => false, 'msg' => 'Server could not save the file.'];
        }

        $db = new DB();
        $r = $db->insertUpdate(
            "INSERT INTO tender_submission_attachments
                (tender_id, kind, label, original_filename, stored_path, mime_type, size_bytes, notes,
                 uploaded_by, uploaded_by_name, uploaded_on)
             VALUES (:t, :k, :l, :of, :sp, :m, :sz, :n, :u, :un, NOW())",
            [
                ':t' => $tenderId, ':k' => $kind, ':l' => $label,
                ':of' => $origName, ':sp' => $rel, ':m' => $mime, ':sz' => $size,
                ':n' => (string)($meta['notes'] ?? ''),
                ':u' => $userId, ':un' => $userName,
            ]
        );
        return ['ok' => true, 'id' => (int)($r['last_id'] ?? 0)];
    }

    /** Soft-delete + best-effort remove file from disk. Row survives for audit trail. */
    public function remove(int $id, int $userId): array
    {
        $cur = $this->find($id);
        if (!$cur) return ['ok' => false, 'msg' => 'Attachment not found.'];
        $db = new DB();
        $db->insertUpdate(
            "UPDATE tender_submission_attachments SET deleted = 1, uploaded_by = :u WHERE c0d = :id",
            [':u' => $userId, ':id' => $id]
        );
        // Try to delete the file too (audit row survives via deleted=1).
        $abs = ROOT . '/' . ltrim((string)$cur['stored_path'], '/');
        if (is_file($abs)) @unlink($abs);
        return ['ok' => true];
    }

    /** Absolute filesystem path for download. Null if the file is missing on disk. */
    public function absolutePath(array $row): ?string
    {
        $abs = ROOT . '/' . ltrim((string)$row['stored_path'], '/');
        return is_file($abs) ? $abs : null;
    }
}
