<?php

declare(strict_types=1);

/**
 * Workflow-governance helpers introduced in v0.5.9.8.16.
 *
 * Clinical identity, financial correction, cashier exception routing, and
 * in-house service billing are deliberately separated here so no role can
 * borrow another professional's signature or silently invent a charge.
 */

function workflow_governance_schema_ready(): bool
{
    return schema_table_exists(table('billing_correction_requests'))
        && schema_table_exists(table('financial_correction_addenda'))
        && schema_table_exists(table('clinical_encounter_transfers'))
        && schema_column_exists(table('clinical_encounters'), 'paused_at')
        && schema_column_exists(table('inhouse_checkup_results'), 'approved_fee_snapshot')
        && schema_column_exists(table('invoice_items'), 'inhouse_checkup_result_id');
}

function governance_open_billing_corrections(PDO $pdo, int $encounterId = 0, int $queueId = 0): array
{
    if (!workflow_governance_schema_ready()) return [];
    $where = [];
    $args = [];
    if ($encounterId > 0) { $where[] = 'b.encounter_id=?'; $args[] = $encounterId; }
    if ($queueId > 0) { $where[] = 'b.queue_entry_id=?'; $args[] = $queueId; }
    if (!$where) return [];
    $stmt = $pdo->prepare(
        'SELECT b.*,u.name requested_by_name,d.name assigned_dentist_name '
        . 'FROM `' . table('billing_correction_requests') . '` b '
        . 'LEFT JOIN `' . table('users') . '` u ON u.id=b.requested_by '
        . 'LEFT JOIN `' . table('users') . '` d ON d.id=b.assigned_dentist_user_id '
        . 'WHERE (' . implode(' OR ', $where) . ') AND b.status IN ("open","in_review") ORDER BY b.requested_at DESC,b.id DESC'
    );
    $stmt->execute($args);
    return $stmt->fetchAll() ?: [];
}

function governance_request_billing_correction(
    PDO $pdo,
    array $queue,
    ?array $encounter,
    ?array $invoice,
    int $requestedBy,
    string $reason
): array {
    if (!workflow_governance_schema_ready()) throw new RuntimeException('Apply Version 0.5.9.8.16 before returning a visit for billing correction.');
    if (mb_strlen(trim($reason)) < 10) throw new RuntimeException('Enter a specific billing-correction reason of at least 10 characters.');
    $patientId = (int)($queue['patient_id'] ?? $encounter['patient_id'] ?? 0);
    $queueId = (int)($queue['id'] ?? 0);
    $encounterId = (int)($encounter['id'] ?? 0);
    if ($patientId < 1 || $queueId < 1) throw new RuntimeException('The patient and queue visit could not be verified for billing correction.');

    $missing = [];
    if ($encounterId > 0) {
        $stmt = $pdo->prepare(
            'SELECT * FROM `' . table('procedures') . '` WHERE encounter_id=? AND status="completed" '
            . 'AND (billing_status="blocked" OR fee_status="blocked" OR approved_fee_snapshot IS NULL OR (service_id IS NULL AND treatment_plan_item_id IS NULL)) ORDER BY id FOR UPDATE'
        );
        $stmt->execute([$encounterId]);
        $missing = $stmt->fetchAll() ?: [];
    }
    if (!$missing) throw new RuntimeException('No completed procedure currently requires billing correction. Reload the register before returning the visit.');

    $assignedDentistId = (int)($encounter['dentist_user_id'] ?? $queue['dentist_user_id'] ?? 0) ?: null;
    $existing = $pdo->prepare(
        'SELECT id FROM `' . table('billing_correction_requests') . '` WHERE queue_entry_id=? AND procedure_id=? AND status IN ("open","in_review") LIMIT 1 FOR UPDATE'
    );
    $insert = $pdo->prepare(
        'INSERT INTO `' . table('billing_correction_requests') . '` '
        . '(branch_id,patient_id,queue_entry_id,appointment_id,encounter_id,invoice_id,procedure_id,assigned_dentist_user_id,reason,status,requested_by,requested_at,created_at) '
        . 'VALUES (?,?,?,?,?,?,?,?,?,"open",?,NOW(),NOW())'
    );
    $ids = [];
    foreach ($missing as $procedure) {
        $existing->execute([$queueId,(int)$procedure['id']]);
        $existingId = (int)($existing->fetchColumn() ?: 0);
        if ($existingId > 0) { $ids[] = $existingId; continue; }
        $insert->execute([
            (int)($queue['branch_id'] ?? $encounter['branch_id'] ?? 0) ?: null,
            $patientId,$queueId,(int)($queue['appointment_id'] ?? $encounter['appointment_id'] ?? 0) ?: null,
            $encounterId ?: null,(int)($invoice['id'] ?? 0) ?: null,(int)$procedure['id'],$assignedDentistId,
            trim($reason),$requestedBy,
        ]);
        $ids[] = (int)$pdo->lastInsertId();
    }
    return $ids;
}

function governance_resolve_billing_corrections(PDO $pdo, int $encounterId, int $procedureId, int $resolvedBy, string $note): void
{
    if (!workflow_governance_schema_ready()) return;
    $queueStmt=$pdo->prepare('SELECT DISTINCT queue_entry_id FROM `' . table('billing_correction_requests') . '` WHERE encounter_id=? AND procedure_id=? AND status IN ("open","in_review") AND queue_entry_id IS NOT NULL FOR UPDATE');
    $queueStmt->execute([$encounterId,$procedureId]);
    $queueIds=array_map('intval',$queueStmt->fetchAll(PDO::FETCH_COLUMN)?:[]);
    $stmt = $pdo->prepare(
        'UPDATE `' . table('billing_correction_requests') . '` SET status="corrected",resolved_by=?,resolved_at=NOW(),resolution_note=?,updated_at=NOW() '
        . 'WHERE encounter_id=? AND procedure_id=? AND status IN ("open","in_review")'
    );
    $stmt->execute([$resolvedBy,mb_substr(trim($note),0,1000),$encounterId,$procedureId]);
    foreach($queueIds as $queueId){
        $open=$pdo->prepare('SELECT COUNT(*) FROM `' . table('billing_correction_requests') . '` WHERE queue_entry_id=? AND status IN ("open","in_review")');
        $open->execute([$queueId]);
        if((int)$open->fetchColumn()>0)continue;
        $update=$pdo->prepare('UPDATE `' . table('queue_entries') . '` SET status="waiting_billing",billing_at=COALESCE(billing_at,NOW()),updated_at=NOW() WHERE id=? AND status="billing_correction"');
        $update->execute([$queueId]);
        if($update->rowCount()===1){
            $pdo->prepare('INSERT INTO `' . table('queue_status_history') . '` (queue_entry_id,previous_status,new_status,notes,changed_by,created_at) VALUES (?,"billing_correction","waiting_billing","Billing correction completed; returned to Cashier",?,NOW())')->execute([$queueId,$resolvedBy]);
        }
    }
}

function governance_record_financial_addendum(PDO $pdo, array $context, string $type, string $reason, array $oldValue, array $newValue, int $authorUserId): int
{
    if (!workflow_governance_schema_ready()) throw new RuntimeException('Workflow-governance schema is not ready.');
    $allowed = ['service_mapping','fee_policy','insurance','discount','no_charge','other'];
    if (!in_array($type,$allowed,true)) $type = 'other';
    $user = current_user() ?? [];
    $authorName = trim((string)($user['name'] ?? 'Authorized administrator'));
    $createdAt = date('Y-m-d H:i:s');
    $oldJson = json_encode($oldValue,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    $newJson = json_encode($newValue,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    $hash = hash('sha256',implode('|',[
        (string)($context['patient_id'] ?? 0),(string)($context['encounter_id'] ?? 0),(string)($context['invoice_id'] ?? 0),
        $type,$reason,(string)$oldJson,(string)$newJson,(string)$authorUserId,$createdAt,
    ]));
    $pdo->prepare(
        'INSERT INTO `' . table('financial_correction_addenda') . '` '
        . '(branch_id,patient_id,encounter_id,invoice_id,procedure_id,correction_type,reason,old_value_json,new_value_json,author_user_id,author_name_snapshot,created_at,addendum_hash) '
        . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)'
    )->execute([
        (int)($context['branch_id'] ?? 0) ?: null,(int)($context['patient_id'] ?? 0),(int)($context['encounter_id'] ?? 0) ?: null,
        (int)($context['invoice_id'] ?? 0) ?: null,(int)($context['procedure_id'] ?? 0) ?: null,$type,mb_substr(trim($reason),0,1000),
        $oldJson ?: null,$newJson ?: null,$authorUserId,$authorName,$createdAt,$hash,
    ]);
    return (int)$pdo->lastInsertId();
}

function governance_inhouse_fee_source(PDO $pdo, int $serviceId, int $branchId): array
{
    $source = clinical_billing_service($pdo,$serviceId,$branchId);
    if (!$source) throw new RuntimeException('The in-house service is missing from the active service catalogue.');
    clinical_billing_assert_source($source);
    return $source;
}

function governance_inhouse_invoice_number(PDO $pdo, int $resultId): string
{
    $base = 'INV-IH-' . date('ymd') . '-' . str_pad((string)$resultId,6,'0',STR_PAD_LEFT);
    $candidate = $base;
    $stmt = $pdo->prepare('SELECT COUNT(*) FROM `' . table('invoices') . '` WHERE invoice_no=?');
    for ($i=0;$i<10;$i++) {
        $stmt->execute([$candidate]);
        if ((int)$stmt->fetchColumn() === 0) return $candidate;
        $candidate = $base . '-' . random_int(10,99);
    }
    throw new RuntimeException('A unique invoice number could not be created for the in-house service.');
}

function governance_sync_inhouse_invoice(PDO $pdo, array $queue, int $resultId, array $source, int $userId): int
{
    if (!workflow_governance_schema_ready()) throw new RuntimeException('Apply Version 0.5.9.8.16 before completing an in-house service.');
    $resultStmt = $pdo->prepare('SELECT * FROM `' . table('inhouse_checkup_results') . '` WHERE id=? FOR UPDATE');
    $resultStmt->execute([$resultId]);
    $result = $resultStmt->fetch();
    if (!$result) throw new RuntimeException('The in-house result could not be verified for billing.');

    $feeStatus = (string)$source['fee_status'];
    $amount = $feeStatus === 'no_charge' ? 0.0 : round((float)$source['approved_fee'],2);
    $invoice = null;
    if ((int)($result['invoice_id'] ?? 0) > 0) {
        $stmt = $pdo->prepare('SELECT * FROM `' . table('invoices') . '` WHERE id=? FOR UPDATE');
        $stmt->execute([(int)$result['invoice_id']]);
        $invoice = $stmt->fetch() ?: null;
    }
    if (!$invoice) {
        $stmt = $pdo->prepare('SELECT * FROM `' . table('invoices') . '` WHERE queue_entry_id=? AND status NOT IN ("voided","refunded") ORDER BY id DESC LIMIT 1 FOR UPDATE');
        $stmt->execute([(int)$queue['id']]);
        $invoice = $stmt->fetch() ?: null;
    }
    if ($invoice && ((float)($invoice['paid_amount'] ?? 0) > 0.001 || !empty($invoice['charge_locked_at']))) {
        throw new RuntimeException('This in-house invoice already has payment or approval activity and cannot be regenerated.');
    }
    if (!$invoice) {
        $invoiceNo = governance_inhouse_invoice_number($pdo,$resultId);
        $pdo->prepare(
            'INSERT INTO `' . table('invoices') . '` '
            . '(branch_id,invoice_no,patient_id,encounter_id,queue_entry_id,description,subtotal,discount_type,discount_value,discount_amount,tax_amount,amount,paid_amount,status,created_by,created_at,updated_at) '
            . 'VALUES (?,?,?,?,?,?,0,"none",0,0,0,0,0,"unpaid",?,NOW(),NOW())'
        )->execute([
            (int)($queue['branch_id'] ?? 0) ?: null,$invoiceNo,(int)$queue['patient_id'],null,(int)$queue['id'],
            (string)($source['procedure_name'] ?? $queue['service_name'] ?? 'In-house service'),$userId,
        ]);
        $invoiceId = (int)$pdo->lastInsertId();
    } else {
        $invoiceId = (int)$invoice['id'];
        $pdo->prepare('DELETE FROM `' . table('invoice_items') . '` WHERE invoice_id=? AND inhouse_checkup_result_id=?')->execute([$invoiceId,$resultId]);
    }

    $description = trim((string)($source['procedure_name'] ?? $queue['service_name'] ?? 'In-house service'));
    $notes = $feeStatus === 'no_charge' ? 'Authorized no-charge: ' . trim((string)($source['no_charge_reason'] ?? '')) : null;
    $payload = [
        'source_type'=>'inhouse_service','result_id'=>$resultId,'service_id'=>(int)$source['service_id'],
        'description'=>$description,'amount'=>$amount,'fee_status'=>$feeStatus,
    ];
    $hash = hash('sha256',(string)json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
    $pdo->prepare(
        'INSERT INTO `' . table('invoice_items') . '` '
        . '(invoice_id,service_id,inhouse_checkup_result_id,source_type,item_code,description,quantity,unit_price,gross_amount,discount_amount,net_amount,notes,source_authorized_by,source_authorized_at,source_hash,created_by,created_at) '
        . 'VALUES (?,?,?,"service_catalog",?,?,1,?,?,0,?,?,?,?,?,?,NOW())'
    )->execute([
        $invoiceId,(int)$source['service_id'],$resultId,'INHOUSE-' . $resultId,$description,$amount,$amount,$amount,$notes,
        (int)($source['fee_authorized_by'] ?? 0) ?: $userId,$source['fee_authorized_at'] ?? date('Y-m-d H:i:s'),$hash,$userId,
    ]);
    $pdo->prepare(
        'UPDATE `' . table('invoices') . '` SET description=?,subtotal=?,discount_type="none",discount_value=0,discount_amount=0,tax_amount=0,amount=?,status="unpaid",charge_source_hash=?,updated_at=NOW() WHERE id=?'
    )->execute([$description,$amount,$amount,$hash,$invoiceId]);
    $pdo->prepare(
        'UPDATE `' . table('inhouse_checkup_results') . '` SET fee_status=?,approved_fee_snapshot=?,no_charge_reason=?,fee_authorized_by=?,fee_authorized_at=?,invoice_id=?,billing_status=?,updated_at=NOW() WHERE id=?'
    )->execute([
        $feeStatus,$amount,trim((string)($source['no_charge_reason'] ?? '')) ?: null,(int)($source['fee_authorized_by'] ?? 0) ?: $userId,
        $source['fee_authorized_at'] ?? date('Y-m-d H:i:s'),$invoiceId,$feeStatus === 'no_charge' ? 'no_charge' : 'invoiced',$resultId,
    ]);
    return $invoiceId;
}

function governance_encounter_transfer_row(PDO $pdo, int $transferId, bool $lock = false): ?array
{
    $stmt = $pdo->prepare(
        'SELECT t.*,f.name from_dentist_name,tu.name to_dentist_name FROM `' . table('clinical_encounter_transfers') . '` t '
        . 'LEFT JOIN `' . table('users') . '` f ON f.id=t.from_dentist_user_id '
        . 'LEFT JOIN `' . table('users') . '` tu ON tu.id=t.to_dentist_user_id WHERE t.id=? LIMIT 1' . ($lock ? ' FOR UPDATE' : '')
    );
    $stmt->execute([$transferId]);
    return $stmt->fetch() ?: null;
}

function governance_encounter_transfers(PDO $pdo, int $encounterId): array
{
    if (!workflow_governance_schema_ready()) return [];
    $stmt = $pdo->prepare(
        'SELECT t.*,f.name from_dentist_name,tu.name to_dentist_name FROM `' . table('clinical_encounter_transfers') . '` t '
        . 'LEFT JOIN `' . table('users') . '` f ON f.id=t.from_dentist_user_id '
        . 'LEFT JOIN `' . table('users') . '` tu ON tu.id=t.to_dentist_user_id WHERE t.encounter_id=? ORDER BY t.requested_at DESC,t.id DESC'
    );
    $stmt->execute([$encounterId]);
    return $stmt->fetchAll() ?: [];
}
