<?php
/**
 * ProjectOpsDocuments · Slice 8Z-v.
 *
 * PM/PC-owned document library on a services project — signed contract, CAR /
 * WCF / NSSF / vehicle insurance, OSHA method statement, TRA e-fiscal cert,
 * site access permits, HSE plan. Subtypes are Tanzania-tuned.
 *
 * Storage mirrors TenderFmVerificationAttachments: files under
 * ROOT/uploads/projects/{projectId}/ops-docs/. 10 MB per file. Soft-delete.
 */
class ProjectOpsDocuments extends Model
{
    /** Labels + mandatory flag per subtype (mandatory subtypes gate contract activation). */
    public const SUBTYPES = [
        'SIGNED_CONTRACT'        => ['label' => 'Signed contract (client PDF)',        'mandatory' => true],
        'CAR_INSURANCE'          => ['label' => "Contractor's All-Risk (CAR) insurance",'mandatory' => true],
        'WORKERS_COMP'           => ['label' => 'Workers Compensation policy',         'mandatory' => true],
        'WCF_PROOF'              => ['label' => 'WCF contribution proof',              'mandatory' => true],
        'NSSF_PROOF'             => ['label' => 'NSSF contribution proof',             'mandatory' => true],
        'OSHA_METHOD_STATEMENT'  => ['label' => 'OSHA site method statement',          'mandatory' => true],
        'TRA_EFISCAL_CERT'       => ['label' => 'TRA e-fiscal invoicing certificate',  'mandatory' => true],
        'SITE_ACCESS_PERMIT'     => ['label' => 'Site access permit',                  'mandatory' => false],
        'HSE_PLAN'               => ['label' => 'Health · Safety · Environment plan',  'mandatory' => false],
        'VEHICLE_INSURANCE'      => ['label' => 'FS vehicle insurance',                'mandatory' => false],
        'OTHER'                  => ['label' => 'Other',                               'mandatory' => false],
    ];

    public const MAX_BYTES = 10 * 1024 * 1024;

    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',
    ];

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

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

    public function upload(int $projectId, 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).'];

        $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.'];
        }

        $subtype = strtoupper((string)($meta['subtype'] ?? 'OTHER'));
        if (!isset(self::SUBTYPES[$subtype])) $subtype = 'OTHER';
        $label = trim((string)($meta['label'] ?? '')) ?: (string)self::SUBTYPES[$subtype]['label'];
        $validity = trim((string)($meta['validity_until'] ?? '')) ?: null;

        $origName = basename((string)$file['name']);
        $safe = preg_replace('/[^A-Za-z0-9._-]+/', '_', $origName);
        if (strlen($safe) > 120) $safe = substr($safe, -120);
        $dir = ROOT . '/uploads/projects/' . $projectId . '/ops-docs';
        if (!is_dir($dir) && !@mkdir($dir, 0775, true)) return ['ok' => false, 'msg' => 'Server could not create upload directory.'];
        $fname = uniqid('ops', true) . '_' . $safe;
        $abs   = $dir . '/' . $fname;
        $rel   = 'uploads/projects/' . $projectId . '/ops-docs/' . $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 project_ops_documents
                (project_id, subtype, label, original_filename, stored_path, mime_type, size_bytes,
                 validity_until, notes, uploaded_by, uploaded_by_name, uploaded_on)
             VALUES (:p, :st, :l, :of, :sp, :m, :sz, :vu, :n, :u, :un, NOW())",
            [
                ':p' => $projectId, ':st' => $subtype, ':l' => $label,
                ':of' => $origName, ':sp' => $rel, ':m' => $mime, ':sz' => $size,
                ':vu' => $validity, ':n' => (string)($meta['notes'] ?? ''),
                ':u' => $userId, ':un' => $userName,
            ]
        );
        return ['ok' => true, 'id' => (int)($r['last_id'] ?? 0)];
    }

    public function remove(int $id, int $userId): array
    {
        $cur = $this->find($id);
        if (!$cur) return ['ok' => false, 'msg' => 'Document not found.'];
        $db = new DB();
        $db->insertUpdate(
            "UPDATE project_ops_documents SET deleted = 1, uploaded_by = :u WHERE c0d = :id",
            [':u' => $userId, ':id' => $id]
        );
        $abs = ROOT . '/' . ltrim((string)$cur['stored_path'], '/');
        if (is_file($abs)) @unlink($abs);
        return ['ok' => true];
    }

    public function absolutePath(array $row): ?string
    {
        $abs = ROOT . '/' . ltrim((string)$row['stored_path'], '/');
        return is_file($abs) ? $abs : null;
    }

    /**
     * Mandatory-N gate for the activation checklist. Returns
     * ['ok' => bool, 'missing' => [SUBTYPE_KEY,...], 'labels' => ['label',...]].
     */
    public function mandatoryCheck(int $projectId): array
    {
        $rows = $this->listFor($projectId);
        $present = [];
        foreach ($rows as $r) $present[(string)$r['subtype']] = true;
        $missing = []; $labels = [];
        foreach (self::SUBTYPES as $k => $meta) {
            if (empty($meta['mandatory'])) continue;
            if (empty($present[$k])) { $missing[] = $k; $labels[] = $meta['label']; }
        }
        return ['ok' => empty($missing), 'missing' => $missing, 'labels' => $labels];
    }
}
