<?php
/**
 * Billing-sheet view / download (coding.work / coding.bs_upload / coding.supervise / coding.audit /
 * coding.validate).  bs-view-v1
 *
 * Streams a stored billing-sheet file after a SCOPE check: a coder may only open sheets for a
 * client»process»facility»provider tuple in their coding_user_scope; supervisors/managers (unrestricted)
 * and billing-sheet uploaders (global) may open any. ?dl=1 forces a download; otherwise inline (new tab).
 * The on-disk path is resolved from the DB row (never user input) → no path traversal. PHI access is logged.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

$id  = (int) ($_GET['id'] ?? 0);
$st  = $pdo->prepare("SELECT * FROM coding_billing_sheets WHERE id=?"); $st->execute([$id]); $row = $st->fetch();
if (!$row) { http_response_code(404); exit('Billing sheet not found.'); }

// Scope gate: an in-scope coder, OR an unrestricted user (sysadmin / supervisor / manager), OR a
// billing-sheet uploader (uploads are global, so viewing is too). A SCRIBE sheet has no billing provider
// (provider_id NULL), so it is gated at the FACILITY level — any coder who works that client»process»facility.
$isScribeSheet = empty($row['provider_id']);
if ($isScribeSheet) {
    $q = $pdo->prepare("SELECT 1 FROM coding_user_scope WHERE user_id=? AND " . coding_vertical_scope_sql('vertical_id') . "
        AND client_id=? AND (process_id IS NULL OR process_id=?) AND (facility_id IS NULL OR facility_id=?) LIMIT 1");
    $q->execute([$uid, (int) $row['client_id'], (int) $row['process_id'], (int) $row['facility_id']]);
    $inScope = (bool) $q->fetchColumn();
} else {
    $inScope = coding_user_can_tuple($uid, (int) $row['client_id'], (int) $row['process_id'], (int) $row['facility_id'], (int) $row['provider_id']);
}
$allowed = coding_scope_unrestricted($uid) || can('coding.bs_upload') || $inScope;
if (!$allowed) { http_response_code(403); exit('You do not have access to this billing sheet.'); }

$path = rtrim(BILLING_SHEETS_DIR, "/\\") . '/' . basename((string) $row['stored_name']);
if (!is_file($path)) {
    http_response_code(404);
    // SysAdmins get the looked-for path to diagnose a missing / relocated PHI store; others get a plain message.
    exit(is_sysadmin()
        ? 'File missing on the server. Looked for: ' . $path . ' — confirm this folder exists and was preserved across deploys (see config.php BILLING_SHEETS_DIR).'
        : 'File missing on the server.');
}

audit_log('coding.bs.view', 'coding_billing_sheets', $id, null,
    ['client'=>(int)$row['client_id'], 'provider'=>(int)$row['provider_id'], 'dl'=>!empty($_GET['dl'])]);

$dl    = !empty($_GET['dl']);
$mime  = $row['mime'] ?: 'application/octet-stream';
$fname = preg_replace('/["\r\n]/', '', (string) ($row['original_name'] ?: ('billing-sheet-' . $id)));
while (ob_get_level() > 0) ob_end_clean();
header('Content-Type: ' . $mime);
header('Content-Length: ' . (string) filesize($path));
header('Content-Disposition: ' . ($dl ? 'attachment' : 'inline') . '; filename="' . $fname . '"');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: private, no-store');
readfile($path);
exit;
