<?php
declare(strict_types=1);

require_once __DIR__ . '/../config/bootstrap.php';

require_permission($conn, 'booking');
process_post_only_or_redirect('../pages/booking.php');

ensure_post_csrf_or_redirect('booking_save', '../pages/booking.php');

ensure_order_branch_column($conn);
ensure_order_return_column($conn);
ensure_order_slide_send_column($conn);
ensure_order_service_at_column($conn);
ensure_order_source_column($conn);
ensure_order_deposit_method_column($conn);
ensure_order_split_deposit_columns($conn);
ensure_order_bill_number_column($conn);
ensure_order_detail_options_column($conn);
ensure_order_detail_item_note_column($conn);
ensure_payment_slip_columns($conn);

$customerName = trim((string) ($_POST['customer_name'] ?? ''));
$phone = trim((string) ($_POST['phone'] ?? ''));
$phoneDigits = normalize_phone($phone);
$plateNumber = normalize_plate((string) ($_POST['plate_number'] ?? ''));
$province = trim((string) ($_POST['province'] ?? ''));
$vehicleType = trim((string) ($_POST['vehicle_type'] ?? ''));
$hasVehicleTypeInput = array_key_exists('vehicle_type', $_POST);
$brand = trim((string) ($_POST['brand'] ?? ''));
$model = trim((string) ($_POST['model'] ?? ''));
$color = trim((string) ($_POST['color'] ?? ''));
$branchName = normalize_branch_name((string) ($_POST['branch_name'] ?? ''));
$scheduledAtDateRaw = trim((string) ($_POST['scheduled_at_date'] ?? ''));
$scheduledAtTimeRaw = trim((string) ($_POST['scheduled_at_time'] ?? ''));
$slideSendAtDateRaw = trim((string) ($_POST['slide_send_at_date'] ?? ''));
$slideSendAtTimeRaw = trim((string) ($_POST['slide_send_at_time'] ?? ''));
$serviceAtDateRaw = trim((string) ($_POST['service_at_date'] ?? ''));
$serviceAtTimeRaw = trim((string) ($_POST['service_at_time'] ?? ''));
$returnAtDateRaw = trim((string) ($_POST['return_at_date'] ?? ''));
$returnAtTimeRaw = trim((string) ($_POST['return_at_time'] ?? ''));
$scheduledAtRaw = trim((string) ($_POST['scheduled_at'] ?? ''));
$slideSendAtRaw = trim((string) ($_POST['slide_send_at'] ?? ''));
$serviceAtRaw = trim((string) ($_POST['service_at'] ?? ''));
$returnAtRaw = trim((string) ($_POST['return_at'] ?? ''));
$totalRaw = str_replace(',', '', trim((string) ($_POST['total_price'] ?? '')));
$adminDepositRaw = str_replace(',', '', trim((string) ($_POST['admin_deposit_amount'] ?? '')));
$adminDepositMethod = trim((string) ($_POST['admin_deposit_method'] ?? 'cash'));
$branchDepositRaw = str_replace(',', '', trim((string) ($_POST['branch_deposit_amount'] ?? '')));
$branchDepositMethod = trim((string) ($_POST['branch_deposit_method'] ?? 'cash'));
$adminDepositSlipFile = $_FILES['admin_deposit_slip_file'] ?? null;
$branchDepositSlipFile = $_FILES['branch_deposit_slip_file'] ?? null;
$filmFrontServiceId = (int) ($_POST['film_front_service_id'] ?? 0);
$filmSunroofServiceId = (int) ($_POST['film_sunroof_service_id'] ?? 0);
$filmFrontSideServiceId = (int) ($_POST['film_front_side_service_id'] ?? 0);
$filmRearSideServiceId = (int) ($_POST['film_rear_side_service_id'] ?? 0);
$filmRearServiceId = (int) ($_POST['film_rear_service_id'] ?? 0);
$filmNotes = trim((string) ($_POST['film_notes'] ?? ''));
$notes = trim((string) ($_POST['notes'] ?? ''));
$serviceIds = array_values(array_unique(array_filter(array_map('intval', (array) ($_POST['service_ids'] ?? array())))));
$serviceNotesInput = array();
foreach ((array) ($_POST['service_notes'] ?? array()) as $serviceId => $serviceNote) {
    $serviceId = (int) $serviceId;
    $serviceNote = trim((string) $serviceNote);

    if ($serviceId > 0) {
        $serviceNotesInput[$serviceId] = $serviceNote;
    }
}
$serviceGroupChoiceInput = array();
foreach ((array) ($_POST['service_group_choice'] ?? array()) as $mainServiceId => $groupId) {
    $mainServiceId = (int) $mainServiceId;
    $groupId = (int) $groupId;

    if ($mainServiceId > 0 && $groupId > 0) {
        $serviceGroupChoiceInput[$mainServiceId] = $groupId;
    }
}
$packageUsageInput = array();
foreach ((array) ($_POST['package_usage'] ?? array()) as $serviceId => $customerPackageItemId) {
    $serviceId = (int) $serviceId;
    $customerPackageItemId = (int) $customerPackageItemId;

    if ($serviceId > 0 && $customerPackageItemId > 0) {
        $packageUsageInput[$serviceId] = $customerPackageItemId;
    }
}

$bookingTotalRequiredMessage = localized_flash_message(
    'ยอดรวมบิลต้องมากกว่า 0 ก่อนเปิดบิล',
    'Booking total must be greater than 0 before opening the bill.'
);

$oldInput = array(
    'customer_name' => $customerName,
    'lookup_phone' => trim((string) ($_POST['lookup_phone'] ?? $phone)),
    'phone' => $phone,
    'plate_number' => $plateNumber,
    'province' => $province,
    'vehicle_type' => $vehicleType,
    'brand' => $brand,
    'model' => $model,
    'color' => $color,
    'branch_name' => $branchName,
    'scheduled_at' => $scheduledAtRaw,
    'scheduled_at_date' => $scheduledAtDateRaw,
    'scheduled_at_time' => $scheduledAtTimeRaw,
    'slide_send_at' => $slideSendAtRaw,
    'slide_send_at_date' => $slideSendAtDateRaw,
    'slide_send_at_time' => $slideSendAtTimeRaw,
    'service_at' => $serviceAtRaw,
    'service_at_date' => $serviceAtDateRaw,
    'service_at_time' => $serviceAtTimeRaw,
    'return_at' => $returnAtRaw,
    'return_at_date' => $returnAtDateRaw,
    'return_at_time' => $returnAtTimeRaw,
    'total_price' => $totalRaw,
    'admin_deposit_amount' => $adminDepositRaw,
    'admin_deposit_method' => $adminDepositMethod,
    'branch_deposit_amount' => $branchDepositRaw,
    'branch_deposit_method' => $branchDepositMethod,
    'film_front_service_id' => (string) $filmFrontServiceId,
    'film_sunroof_service_id' => (string) $filmSunroofServiceId,
    'film_front_side_service_id' => (string) $filmFrontSideServiceId,
    'film_rear_side_service_id' => (string) $filmRearSideServiceId,
    'film_rear_service_id' => (string) $filmRearServiceId,
    'film_notes' => $filmNotes,
    'notes' => $notes,
    'service_ids' => $serviceIds,
    'service_notes' => $serviceNotesInput,
    'service_group_choice' => $serviceGroupChoiceInput,
    'package_usage' => $packageUsageInput,
);

$canCollectAdminDeposit = can_access($conn, 'booking_deposit_admin');
$canCollectBranchDeposit = can_access($conn, 'booking_deposit_branch');

if (!$canCollectAdminDeposit) {
    $adminDepositRaw = '';
    $adminDepositMethod = 'cash';
    $adminDepositSlipFile = null;
    $oldInput['admin_deposit_amount'] = '';
    $oldInput['admin_deposit_method'] = 'cash';
}

if (!$canCollectBranchDeposit) {
    $branchDepositRaw = '';
    $branchDepositMethod = 'cash';
    $branchDepositSlipFile = null;
    $oldInput['branch_deposit_amount'] = '';
    $oldInput['branch_deposit_method'] = 'cash';
}

if ($customerName === '' || $plateNumber === '') {
    redirect_with_flash('../pages/booking.php', 'error', localized_flash_message('กรุณาระบุชื่อลูกค้าและเลขทะเบียนรถ', 'Customer name and plate number are required.'), $oldInput);
}

$allowedBranches = authorized_order_branch_options($conn);

if ($branchName === '' || !in_array($branchName, $allowedBranches, true)) {
    redirect_with_flash('../pages/booking.php', 'error', localized_flash_message('กรุณาเลือกสาขาที่ให้บริการ', 'Service branch is required.'), $oldInput);
}

if (empty($serviceIds)) {
    redirect_with_flash('../pages/booking.php', 'error', localized_flash_message('กรุณาเลือกรายการบริการอย่างน้อย 1 รายการ', 'Select at least one service item.'), $oldInput);
}

$normalizeDateTime = static function (
    string $dateRaw,
    string $timeRaw,
    string $legacyRaw,
    string|array $missingMessage,
    string|array $invalidMessage,
    array $oldInput
): ?string {
    $dateRaw = trim($dateRaw);
    $timeRaw = trim($timeRaw);
    $legacyRaw = trim($legacyRaw);

    if ($dateRaw === '' && $timeRaw === '' && $legacyRaw === '') {
        return null;
    }

    if ($dateRaw !== '' || $timeRaw !== '') {
        if ($dateRaw === '' || $timeRaw === '') {
            redirect_with_flash('../pages/booking.php', 'error', $missingMessage, $oldInput);
        }

        $timestamp = strtotime($dateRaw . ' ' . $timeRaw);
        if ($timestamp === false) {
            redirect_with_flash('../pages/booking.php', 'error', $invalidMessage, $oldInput);
        }

        return date('Y-m-d H:i:s', $timestamp);
    }

    $timestamp = strtotime($legacyRaw);
    if ($timestamp === false) {
        redirect_with_flash('../pages/booking.php', 'error', $invalidMessage, $oldInput);
    }

    return date('Y-m-d H:i:s', $timestamp);
};

$scheduledAt = $normalizeDateTime(
    $scheduledAtDateRaw,
    $scheduledAtTimeRaw,
    $scheduledAtRaw,
    localized_flash_message('กรุณาเลือกทั้งวันที่และเวลารับสไลด์', 'Please select both date and time for slide pickup.'),
    localized_flash_message('วันเวลารับสไลด์ไม่ถูกต้อง', 'Slide pickup date is invalid.'),
    $oldInput
);

$slideSendAt = $normalizeDateTime(
    $slideSendAtDateRaw,
    $slideSendAtTimeRaw,
    $slideSendAtRaw,
    localized_flash_message('กรุณาเลือกทั้งวันที่และเวลาส่งสไลด์', 'Please select both date and time for slide delivery.'),
    localized_flash_message('วันเวลาส่งสไลด์ไม่ถูกต้อง', 'Slide delivery date is invalid.'),
    $oldInput
);

$serviceAt = $normalizeDateTime(
    $serviceAtDateRaw,
    $serviceAtTimeRaw,
    $serviceAtRaw,
    localized_flash_message('กรุณาเลือกทั้งวันที่และเวลาเข้ารับบริการ', 'Please select both date and time for service usage.'),
    localized_flash_message('วันเวลารับบริการไม่ถูกต้อง', 'Service date is invalid.'),
    $oldInput
);

$returnAt = $normalizeDateTime(
    $returnAtDateRaw,
    $returnAtTimeRaw,
    $returnAtRaw,
    localized_flash_message('กรุณาเลือกทั้งวันที่และเวลารับรถ', 'Please select both date and time for vehicle pickup.'),
    localized_flash_message('วันเวลารับรถไม่ถูกต้อง', 'Vehicle pickup date is invalid.'),
    $oldInput
);

$services = service_catalog_rows($conn, array(
    'service_ids' => $serviceIds,
    'active_only' => true,
));

if (count($services) !== count($serviceIds)) {
    redirect_with_flash('../pages/booking.php', 'error', localized_flash_message('มีบริการที่เลือกอย่างน้อย 1 รายการไม่พร้อมใช้งาน', 'One or more selected services are not available.'), $oldInput);
}

$servicesById = array();
$hasFilmService = false;
foreach ($services as $service) {
    $serviceId = (int) $service['service_id'];
    $servicesById[$serviceId] = $service;

    if (service_row_is_film($service)) {
        $hasFilmService = true;
    }
}
$serviceNames = array_values(array_map(static function (array $service): string {
    return (string) ($service['service_name'] ?? '');
}, $services));

$filmZoneSelectionsInput = array(
    'front' => $filmFrontServiceId,
    'sunroof' => $filmSunroofServiceId,
    'front_side' => $filmFrontSideServiceId,
    'rear_side' => $filmRearSideServiceId,
    'rear' => $filmRearServiceId,
);

$selectedWindowFilmGroup = null;
foreach ($serviceGroupChoiceInput as $mainServiceId => $groupId) {
    $groupRow = service_group_row($conn, $groupId);
    if (!$groupRow || (int) ($groupRow['show_window_film_details'] ?? 0) !== 1) {
        continue;
    }

    $mainServiceSelected = isset($servicesById[$mainServiceId]);
    if (!$mainServiceSelected) {
        foreach ($services as $service) {
            if ((int) ($service['main_service_id'] ?? 0) === $mainServiceId) {
                $mainServiceSelected = true;
                break;
            }
        }
    }

    if ($mainServiceSelected) {
        $selectedWindowFilmGroup = $groupRow;
        break;
    }
}

$validatedFilmZoneSelections = array();
$windowFilmPayloadServiceId = 0;

if ($selectedWindowFilmGroup) {
    $groupId = (int) ($selectedWindowFilmGroup['group_id'] ?? 0);
    $mainServiceId = (int) ($selectedWindowFilmGroup['main_service_id'] ?? 0);
    $filmServiceIds = array_values(array_unique(array_filter(array_map('intval', array_values($filmZoneSelectionsInput)))));
    $filmServiceRows = array();

    if (!empty($filmServiceIds)) {
        foreach (service_catalog_rows($conn, array(
            'service_ids' => $filmServiceIds,
            'active_only' => true,
            'include_inactive_ids' => $filmServiceIds,
        )) as $filmServiceRow) {
            $filmServiceRows[(int) ($filmServiceRow['service_id'] ?? 0)] = $filmServiceRow;
        }
    }

    foreach ($filmZoneSelectionsInput as $zoneKey => $serviceId) {
        if ($serviceId <= 0) {
            continue;
        }

        $serviceRow = $filmServiceRows[$serviceId] ?? null;
        if (
            !$serviceRow
            || (string) ($serviceRow['service_type'] ?? '') !== 'sub_service'
            || (int) ($serviceRow['group_id'] ?? 0) !== $groupId
        ) {
            redirect_with_flash('../pages/booking.php', 'error', localized_flash_message('รายละเอียดฟิล์มกรองแสงที่เลือกไม่ถูกต้อง', 'Window film detail selection is invalid.'), $oldInput);
        }

        $validatedFilmZoneSelections[$zoneKey] = array(
            'service_id' => $serviceId,
            'service_name' => (string) ($serviceRow['service_name'] ?? ''),
        );
    }

    if (empty($validatedFilmZoneSelections)) {
        redirect_with_flash('../pages/booking.php', 'error', localized_flash_message('กรุณาเลือกตัวเลือกฟิล์มกรองแสงอย่างน้อย 1 รายการ', 'Choose at least one window film option.'), $oldInput);
    }

    if (isset($servicesById[$mainServiceId])) {
        $windowFilmPayloadServiceId = $mainServiceId;
    } else {
        foreach ($services as $service) {
            if ((int) ($service['main_service_id'] ?? 0) === $mainServiceId || (int) ($service['group_id'] ?? 0) === $groupId) {
                $windowFilmPayloadServiceId = (int) ($service['service_id'] ?? 0);
                break;
            }
        }
    }
}

$adminSlipFilePath = '';
$branchSlipFilePath = '';
$adminSlipFileName = '';
$branchSlipFileName = '';

$conn->begin_transaction();

try {
    $customerId = 0;
    $carId = 0;
    $customerBefore = array();
    $customerAfter = array();
    $customerAction = '';
    $carBefore = array();
    $carAfter = array();
    $carAction = '';

    $carLookupStmt = db_prepare($conn, "
        SELECT car_id, customer_id
        FROM cars
        WHERE plate_number = ?
        LIMIT 1
    ");
    $carLookupStmt->bind_param('s', $plateNumber);
    $carLookupStmt->execute();
    $carLookupResult = $carLookupStmt->get_result();
    $existingCar = $carLookupResult->fetch_assoc();
    $carLookupStmt->close();

    if ($existingCar) {
        $carId = (int) $existingCar['car_id'];
        $customerId = (int) $existingCar['customer_id'];

        $customerSnapshotStmt = db_prepare($conn, "
            SELECT customer_id, name, phone, customer_group
            FROM customers
            WHERE customer_id = ?
            LIMIT 1
        ");
        $customerSnapshotStmt->bind_param('i', $customerId);
        $customerSnapshotStmt->execute();
        $customerSnapshot = $customerSnapshotStmt->get_result()->fetch_assoc() ?: array();
        $customerSnapshotStmt->close();
        $customerBefore = array(
            'name' => (string) ($customerSnapshot['name'] ?? ''),
            'phone' => (string) ($customerSnapshot['phone'] ?? ''),
            'customer_group' => customer_group_key((string) ($customerSnapshot['customer_group'] ?? 'member')),
        );

        $carSnapshotStmt = db_prepare($conn, "
            SELECT car_id, plate_number, province, vehicle_type, brand, model, color
            FROM cars
            WHERE car_id = ?
            LIMIT 1
        ");
        $carSnapshotStmt->bind_param('i', $carId);
        $carSnapshotStmt->execute();
        $carSnapshot = $carSnapshotStmt->get_result()->fetch_assoc() ?: array();
        $carSnapshotStmt->close();
        $carBefore = array(
            'plate_number' => (string) ($carSnapshot['plate_number'] ?? ''),
            'province' => (string) ($carSnapshot['province'] ?? ''),
            'vehicle_type' => (string) ($carSnapshot['vehicle_type'] ?? ''),
            'brand' => (string) ($carSnapshot['brand'] ?? ''),
            'model' => (string) ($carSnapshot['model'] ?? ''),
            'color' => (string) ($carSnapshot['color'] ?? ''),
        );
        $resolvedVehicleType = $hasVehicleTypeInput
            ? $vehicleType
            : (string) ($carSnapshot['vehicle_type'] ?? '');

        $updateCustomerStmt = db_prepare($conn, "
            UPDATE customers
            SET name = ?, phone = ?, normalized_phone = ?, customer_group = 'member'
            WHERE customer_id = ?
        ");
        $updateCustomerStmt->bind_param('sssi', $customerName, $phone, $phoneDigits, $customerId);
        $updateCustomerStmt->execute();
        $updateCustomerStmt->close();

        $updateCarStmt = db_prepare($conn, "
            UPDATE cars
            SET province = ?, vehicle_type = ?, brand = ?, model = ?, color = ?
            WHERE car_id = ?
        ");
        $updateCarStmt->bind_param('sssssi', $province, $resolvedVehicleType, $brand, $model, $color, $carId);
        $updateCarStmt->execute();
        $updateCarStmt->close();

        $customerAfter = array(
            'name' => $customerName,
            'phone' => $phone,
            'customer_group' => 'member',
        );
        $carAfter = array(
            'plate_number' => $plateNumber,
            'province' => $province,
            'vehicle_type' => $resolvedVehicleType,
            'brand' => $brand,
            'model' => $model,
            'color' => $color,
        );
        $customerAction = 'update';
        $carAction = 'update';
    } else {
        if ($phoneDigits !== '') {
            $customerLookupStmt = db_prepare($conn, "
                SELECT customer_id
                FROM customers
                WHERE normalized_phone = ?
                ORDER BY customer_id DESC
                LIMIT 1
            ");
            $customerLookupStmt->bind_param('s', $phoneDigits);
            $customerLookupStmt->execute();
            $customerLookupResult = $customerLookupStmt->get_result();
            $existingCustomer = $customerLookupResult->fetch_assoc();
            $customerLookupStmt->close();

            if ($existingCustomer) {
                $customerId = (int) $existingCustomer['customer_id'];

                $customerSnapshotStmt = db_prepare($conn, "
                    SELECT customer_id, name, phone, customer_group
                    FROM customers
                    WHERE customer_id = ?
                    LIMIT 1
                ");
                $customerSnapshotStmt->bind_param('i', $customerId);
                $customerSnapshotStmt->execute();
                $customerSnapshot = $customerSnapshotStmt->get_result()->fetch_assoc() ?: array();
                $customerSnapshotStmt->close();
                $customerBefore = array(
                    'name' => (string) ($customerSnapshot['name'] ?? ''),
                    'phone' => (string) ($customerSnapshot['phone'] ?? ''),
                    'customer_group' => customer_group_key((string) ($customerSnapshot['customer_group'] ?? 'member')),
                );

                $updateCustomerStmt = db_prepare($conn, "
                    UPDATE customers
                    SET name = ?, phone = ?, normalized_phone = ?, customer_group = 'member'
                    WHERE customer_id = ?
                ");
                $updateCustomerStmt->bind_param('sssi', $customerName, $phone, $phoneDigits, $customerId);
                $updateCustomerStmt->execute();
                $updateCustomerStmt->close();

                $customerAfter = array(
                    'name' => $customerName,
                    'phone' => $phone,
                    'customer_group' => 'member',
                );
                $customerAction = 'update';
            }
        }

        if ($customerId === 0) {
            $insertCustomerStmt = db_prepare($conn, "
                INSERT INTO customers (name, phone, normalized_phone, customer_group)
                VALUES (?, ?, ?, 'member')
            ");
            $insertCustomerStmt->bind_param('sss', $customerName, $phone, $phoneDigits);
            $insertCustomerStmt->execute();
            $customerId = (int) $conn->insert_id;
            $insertCustomerStmt->close();

            $customerAfter = array(
                'name' => $customerName,
                'phone' => $phone,
                'customer_group' => 'member',
            );
            $customerAction = 'create';
        }

        $insertCarStmt = db_prepare($conn, "
            INSERT INTO cars (customer_id, plate_number, province, vehicle_type, brand, model, color)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        ");
        $insertCarStmt->bind_param('issssss', $customerId, $plateNumber, $province, $vehicleType, $brand, $model, $color);
        $insertCarStmt->execute();
        $carId = (int) $conn->insert_id;
        $insertCarStmt->close();

        $carAfter = array(
            'plate_number' => $plateNumber,
            'province' => $province,
            'vehicle_type' => $vehicleType,
            'brand' => $brand,
            'model' => $model,
            'color' => $color,
        );
        $carAction = 'create';
    }

    $validatedPackageUsages = array();
    foreach ($packageUsageInput as $serviceId => $customerPackageItemId) {
        if (!isset($servicesById[$serviceId])) {
            throw new RuntimeException('Package usage contains an invalid service.');
        }

        $service = $servicesById[$serviceId];
        if ((string) ($service['service_type'] ?? 'sub_service') !== 'sub_service') {
            throw new RuntimeException('Package usage can only be applied to sub services.');
        }

        $balanceRow = service_customer_package_balance_row($conn, $customerId, $customerPackageItemId, $carId);
        if (!$balanceRow || (int) ($balanceRow['sub_service_id'] ?? 0) !== $serviceId) {
            throw new RuntimeException('Selected package balance is not available for this sub service.');
        }

        $validatedPackageUsages[$serviceId] = $balanceRow;
    }

    $serviceTotal = 0.0;
    $packageDiscount = 0.0;
    foreach ($services as $service) {
        $price = (float) $service['price'];
        $serviceTotal += $price;

        if (isset($validatedPackageUsages[(int) $service['service_id']])) {
            $packageDiscount += $price;
        }
    }

    $payableTotal = max(0.0, $serviceTotal - $packageDiscount);

    if ($totalRaw === '') {
        $totalPrice = $payableTotal;
    } elseif (is_numeric($totalRaw)) {
        $totalPrice = max(0.0, (float) $totalRaw);
    } else {
        throw new RuntimeException('Total price is invalid.');
    }

    if ($totalPrice <= 0.0) {
        throw new RuntimeException('__booking_total_must_be_positive__');
    }

    $allowedPaymentMethods = array('cash', 'transfer', 'qr', 'card');
    if (!in_array($adminDepositMethod, $allowedPaymentMethods, true)) {
        $adminDepositMethod = 'cash';
    }

    if (!in_array($branchDepositMethod, $allowedPaymentMethods, true)) {
        $branchDepositMethod = 'cash';
    }

    $hasAdminSlipUpload = is_array($adminDepositSlipFile)
        && (int) ($adminDepositSlipFile['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE;
    $hasBranchSlipUpload = is_array($branchDepositSlipFile)
        && (int) ($branchDepositSlipFile['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE;

    if ($adminDepositRaw === '') {
        $adminDepositAmount = 0.0;
    } elseif (is_numeric($adminDepositRaw)) {
        $adminDepositAmount = max(0.0, (float) $adminDepositRaw);
    } else {
        throw new RuntimeException('Admin deposit amount is invalid.');
    }

    if ($branchDepositRaw === '') {
        $branchDepositAmount = 0.0;
    } elseif (is_numeric($branchDepositRaw)) {
        $branchDepositAmount = max(0.0, (float) $branchDepositRaw);
    } else {
        throw new RuntimeException('Branch deposit amount is invalid.');
    }

    $depositAmount = $adminDepositAmount + $branchDepositAmount;

    if ($depositAmount > $totalPrice) {
        throw new RuntimeException('Total deposit cannot exceed the booking total.');
    }

    if ($hasAdminSlipUpload && $adminDepositAmount <= 0.0) {
        throw new RuntimeException('Attach an Admin deposit slip only when recording a deposit amount.');
    }

    if ($hasBranchSlipUpload && $branchDepositAmount <= 0.0) {
        throw new RuntimeException('Attach a Branch deposit slip only when recording a deposit amount.');
    }

    if ($hasAdminSlipUpload && !payment_method_supports_slip($adminDepositMethod)) {
        throw new RuntimeException('Admin deposit slip upload is available only for Transfer, QR or Card payments.');
    }

    if ($hasBranchSlipUpload && !payment_method_supports_slip($branchDepositMethod)) {
        throw new RuntimeException('Branch deposit slip upload is available only for Transfer, QR or Card payments.');
    }

    $paymentStatus = order_payment_status_from_amounts($totalPrice, $depositAmount);

    $depositMethod = null;
    if ($depositAmount > 0.0) {
        if ($adminDepositAmount > 0.0 && $branchDepositAmount > 0.0 && $adminDepositMethod !== $branchDepositMethod) {
            $depositMethod = 'mixed';
        } elseif ($adminDepositAmount > 0.0) {
            $depositMethod = $adminDepositMethod;
        } elseif ($branchDepositAmount > 0.0) {
            $depositMethod = $branchDepositMethod;
        }
    }

    $insertOrderStmt = db_prepare($conn, "
        INSERT INTO orders (
            customer_id,
            car_id,
            branch_name,
            scheduled_at,
            slide_send_at,
            service_at,
            return_at,
            order_source,
            status,
            payment_status,
            total_price,
            deposit_amount,
            deposit_method,
            admin_deposit_amount,
            admin_deposit_method,
            branch_deposit_amount,
            branch_deposit_method,
            notes
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, 'booking', 'waiting', ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ");
    $insertOrderStmt->bind_param(
        'iissssssddsdsdss',
        $customerId,
        $carId,
        $branchName,
        $scheduledAt,
        $slideSendAt,
        $serviceAt,
        $returnAt,
        $paymentStatus,
        $totalPrice,
        $depositAmount,
        $depositMethod,
        $adminDepositAmount,
        $adminDepositMethod,
        $branchDepositAmount,
        $branchDepositMethod,
        $notes
    );
    $insertOrderStmt->execute();
    $orderId = (int) $conn->insert_id;
    $insertOrderStmt->close();

    $storedAdminSlip = null;
    $storedBranchSlip = null;
    $adminSlipMimeType = '';
    $adminSlipUploadedAt = '';
    $branchSlipMimeType = '';
    $branchSlipUploadedAt = '';

    if ($hasAdminSlipUpload) {
        $storedAdminSlip = store_payment_slip_upload(is_array($adminDepositSlipFile) ? $adminDepositSlipFile : null, 'booking-admin-deposit');
        $adminSlipFilePath = trim((string) ($storedAdminSlip['file_path'] ?? ''));
        $adminSlipFileName = trim((string) ($storedAdminSlip['file_name'] ?? ''));
        $adminSlipMimeType = trim((string) ($storedAdminSlip['mime_type'] ?? ''));
        $adminSlipUploadedAt = trim((string) ($storedAdminSlip['uploaded_at'] ?? ''));
    }

    if ($hasBranchSlipUpload) {
        $storedBranchSlip = store_payment_slip_upload(is_array($branchDepositSlipFile) ? $branchDepositSlipFile : null, 'booking-branch-deposit');
        $branchSlipFilePath = trim((string) ($storedBranchSlip['file_path'] ?? ''));
        $branchSlipFileName = trim((string) ($storedBranchSlip['file_name'] ?? ''));
        $branchSlipMimeType = trim((string) ($storedBranchSlip['mime_type'] ?? ''));
        $branchSlipUploadedAt = trim((string) ($storedBranchSlip['uploaded_at'] ?? ''));
    }

    $adminDepositPaymentId = 0;
    if ($adminDepositAmount > 0.0) {
        $paymentStmt = db_prepare($conn, "
            INSERT INTO payments (
                order_id,
                payment_method,
                amount,
                note,
                slip_file_path,
                slip_file_name,
                slip_mime_type,
                slip_uploaded_at
            )
            VALUES (?, ?, ?, 'Admin deposit at booking', NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''))
        ");
        $paymentStmt->bind_param(
            'isdssss',
            $orderId,
            $adminDepositMethod,
            $adminDepositAmount,
            $adminSlipFilePath,
            $adminSlipFileName,
            $adminSlipMimeType,
            $adminSlipUploadedAt
        );
        $paymentStmt->execute();
        $adminDepositPaymentId = (int) $conn->insert_id;
        $paymentStmt->close();
    }

    $branchDepositPaymentId = 0;
    if ($branchDepositAmount > 0.0) {
        $paymentStmt = db_prepare($conn, "
            INSERT INTO payments (
                order_id,
                payment_method,
                amount,
                note,
                slip_file_path,
                slip_file_name,
                slip_mime_type,
                slip_uploaded_at
            )
            VALUES (?, ?, ?, 'Branch deposit at booking', NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''))
        ");
        $paymentStmt->bind_param(
            'isdssss',
            $orderId,
            $branchDepositMethod,
            $branchDepositAmount,
            $branchSlipFilePath,
            $branchSlipFileName,
            $branchSlipMimeType,
            $branchSlipUploadedAt
        );
        $paymentStmt->execute();
        $branchDepositPaymentId = (int) $conn->insert_id;
        $paymentStmt->close();
    }

    $billNumber = build_order_bill_number($branchName, date('Y-m-d H:i:s'), $orderId);
    $updateBillStmt = db_prepare($conn, "
        UPDATE orders
        SET bill_number = ?
        WHERE order_id = ?
    ");
    $updateBillStmt->bind_param('si', $billNumber, $orderId);
    $updateBillStmt->execute();
    $updateBillStmt->close();

    $packageUsagePayloads = array();
    foreach ($validatedPackageUsages as $serviceId => $balanceRow) {
        $packageUsagePayloads[$serviceId] = service_apply_package_usage($conn, $orderId, $servicesById[$serviceId], $balanceRow);
    }

    $packagePurchasePayloads = array();
    foreach ($services as $service) {
        if ((string) ($service['service_type'] ?? 'sub_service') !== 'package') {
            continue;
        }

        $packagePurchasePayloads[(int) $service['service_id']] = array(
            'items' => service_package_purchase_snapshot($conn, (int) $service['service_id']),
        );
        service_create_customer_package_purchase($conn, $customerId, $carId, $orderId, $service);
    }

    $insertDetailStmt = db_prepare($conn, "
        INSERT INTO order_details (order_id, service_id, service_name_snapshot, price, duration_minutes, service_options, item_note)
        VALUES (?, ?, ?, ?, ?, ?, NULLIF(?, ''))
    ");

    $windowFilmPayload = null;
    if ($selectedWindowFilmGroup && !empty($validatedFilmZoneSelections)) {
        $windowFilmPayload = array(
            'type' => 'window_tint',
            'group_id' => (int) ($selectedWindowFilmGroup['group_id'] ?? 0),
            'group_name' => (string) ($selectedWindowFilmGroup['group_name'] ?? ''),
            'main_service_id' => (int) ($selectedWindowFilmGroup['main_service_id'] ?? 0),
            'front_service_id' => (int) ($validatedFilmZoneSelections['front']['service_id'] ?? 0),
            'front_service_name' => (string) ($validatedFilmZoneSelections['front']['service_name'] ?? ''),
            'sunroof_service_id' => (int) ($validatedFilmZoneSelections['sunroof']['service_id'] ?? 0),
            'sunroof_service_name' => (string) ($validatedFilmZoneSelections['sunroof']['service_name'] ?? ''),
            'front_side_service_id' => (int) ($validatedFilmZoneSelections['front_side']['service_id'] ?? 0),
            'front_side_service_name' => (string) ($validatedFilmZoneSelections['front_side']['service_name'] ?? ''),
            'rear_side_service_id' => (int) ($validatedFilmZoneSelections['rear_side']['service_id'] ?? 0),
            'rear_side_service_name' => (string) ($validatedFilmZoneSelections['rear_side']['service_name'] ?? ''),
            'rear_service_id' => (int) ($validatedFilmZoneSelections['rear']['service_id'] ?? 0),
            'rear_service_name' => (string) ($validatedFilmZoneSelections['rear']['service_name'] ?? ''),
            'notes' => $filmNotes,
        );
    }

    foreach ($services as $service) {
        $serviceId = (int) $service['service_id'];
        $serviceName = (string) $service['service_name'];
        $price = (float) $service['price'];
        $detailPrice = isset($packageUsagePayloads[$serviceId]) ? 0.0 : $price;
        $durationMinutes = 0;
        $serviceOptionsPayload = array();

        if ($windowFilmPayload !== null && $serviceId === $windowFilmPayloadServiceId) {
            $serviceOptionsPayload = array_merge($serviceOptionsPayload, $windowFilmPayload);
        }

        if (isset($packageUsagePayloads[$serviceId])) {
            $serviceOptionsPayload['package_usage'] = $packageUsagePayloads[$serviceId];
        }

        if (isset($packagePurchasePayloads[$serviceId])) {
            $serviceOptionsPayload['package_purchase'] = $packagePurchasePayloads[$serviceId];
        }

        $serviceOptions = empty($serviceOptionsPayload)
            ? null
            : json_encode($serviceOptionsPayload, JSON_UNESCAPED_UNICODE);
        $itemNote = trim((string) ($serviceNotesInput[$serviceId] ?? ''));

        $insertDetailStmt->bind_param('iisdiss', $orderId, $serviceId, $serviceName, $detailPrice, $durationMinutes, $serviceOptions, $itemNote);
        $insertDetailStmt->execute();
    }

    $insertDetailStmt->close();

    $queueNumber = 1;
    $queueResult = $conn->query("
        SELECT COALESCE(MAX(queue_number), 0) + 1 AS next_queue
        FROM queue
        WHERE DATE(created_at) = CURDATE()
    ");

    if ($queueResult instanceof mysqli_result) {
        $queueRow = $queueResult->fetch_assoc();
        $queueNumber = (int) ($queueRow['next_queue'] ?? 1);
    }

    $insertQueueStmt = db_prepare($conn, "
        INSERT INTO queue (order_id, queue_number, status)
        VALUES (?, ?, 'waiting')
    ");
    $insertQueueStmt->bind_param('ii', $orderId, $queueNumber);
    $insertQueueStmt->execute();
    $insertQueueStmt->close();

    audit_log($conn, array(
        'module_key' => 'bill',
        'action_key' => 'create',
        'entity_type' => 'order',
        'entity_id' => $orderId,
        'order_id' => $orderId,
        'customer_id' => $customerId,
        'car_id' => $carId,
        'branch_name' => $branchName,
        'bill_number' => $billNumber,
        'amount' => $totalPrice,
        'message' => 'Booking bill created.',
        'after' => array(
            'order_source' => 'booking',
            'status' => 'waiting',
            'payment_status' => $paymentStatus,
            'queue_number' => $queueNumber,
            'total_price' => $totalPrice,
            'deposit_amount' => $depositAmount,
            'admin_deposit_amount' => $adminDepositAmount,
            'branch_deposit_amount' => $branchDepositAmount,
            'slide_pickup_at' => $scheduledAt,
            'slide_delivery_at' => $slideSendAt,
            'service_at' => $serviceAt,
            'vehicle_pickup_at' => $returnAt,
            'service_count' => count($serviceNames),
            'service_names' => $serviceNames,
            'notes' => $notes,
        ),
        'meta' => array(
            'package_usage_count' => count($packageUsagePayloads),
            'package_purchase_count' => count($packagePurchasePayloads),
        ),
    ));

    if ($adminDepositPaymentId > 0) {
        audit_log($conn, array(
            'module_key' => 'payment',
            'action_key' => 'receive',
            'entity_type' => 'payment',
            'entity_id' => $adminDepositPaymentId,
            'order_id' => $orderId,
            'payment_id' => $adminDepositPaymentId,
            'customer_id' => $customerId,
            'car_id' => $carId,
            'branch_name' => $branchName,
            'bill_number' => $billNumber,
            'amount' => $adminDepositAmount,
            'message' => 'Deposit @Admin received at booking.',
            'after' => array(
                'payment_method' => $adminDepositMethod,
                'recognized_amount' => $adminDepositAmount,
                'received_amount' => $adminDepositAmount,
                'change_amount' => 0,
                'slip_file_name' => $adminSlipFileName,
                'payment_status_after' => $paymentStatus,
            ),
            'meta' => array(
                'deposit_type' => 'admin',
                'order_source' => 'booking',
                'has_slip' => $adminSlipFilePath !== '',
            ),
        ));
    }

    if ($branchDepositPaymentId > 0) {
        audit_log($conn, array(
            'module_key' => 'payment',
            'action_key' => 'receive',
            'entity_type' => 'payment',
            'entity_id' => $branchDepositPaymentId,
            'order_id' => $orderId,
            'payment_id' => $branchDepositPaymentId,
            'customer_id' => $customerId,
            'car_id' => $carId,
            'branch_name' => $branchName,
            'bill_number' => $billNumber,
            'amount' => $branchDepositAmount,
            'message' => 'Deposit @Branch received at booking.',
            'after' => array(
                'payment_method' => $branchDepositMethod,
                'recognized_amount' => $branchDepositAmount,
                'received_amount' => $branchDepositAmount,
                'change_amount' => 0,
                'slip_file_name' => $branchSlipFileName,
                'payment_status_after' => $paymentStatus,
            ),
            'meta' => array(
                'deposit_type' => 'branch',
                'order_source' => 'booking',
                'has_slip' => $branchSlipFilePath !== '',
            ),
        ));
    }

    if ($customerAction !== '') {
        $customerDiff = audit_diff_assoc($customerBefore, $customerAfter);
        if ($customerAction === 'create' || !empty($customerDiff['after'])) {
            audit_log($conn, array(
                'module_key' => 'customer',
                'action_key' => $customerAction,
                'entity_type' => 'customer',
                'entity_id' => $customerId,
                'order_id' => $orderId,
                'customer_id' => $customerId,
                'car_id' => $carId,
                'branch_name' => $branchName,
                'bill_number' => $billNumber,
                'message' => $customerAction === 'create' ? 'Customer created from booking.' : 'Customer updated from booking.',
                'before' => $customerDiff['before'],
                'after' => $customerAction === 'create' ? $customerAfter : $customerDiff['after'],
                'meta' => array(
                    'order_source' => 'booking',
                ),
            ));
        }
    }

    if ($carAction !== '') {
        $carDiff = audit_diff_assoc($carBefore, $carAfter);
        if ($carAction === 'create' || !empty($carDiff['after'])) {
            audit_log($conn, array(
                'module_key' => 'car',
                'action_key' => $carAction,
                'entity_type' => 'car',
                'entity_id' => $carId,
                'order_id' => $orderId,
                'customer_id' => $customerId,
                'car_id' => $carId,
                'branch_name' => $branchName,
                'bill_number' => $billNumber,
                'message' => $carAction === 'create' ? 'Vehicle created from booking.' : 'Vehicle updated from booking.',
                'before' => $carDiff['before'],
                'after' => $carAction === 'create' ? $carAfter : $carDiff['after'],
                'meta' => array(
                    'order_source' => 'booking',
                ),
            ));
        }
    }

    $conn->commit();
    $_SESSION['booking_branch_name'] = $branchName;
    $_SESSION['booking_payment_method'] = $adminDepositAmount > 0.0 ? $adminDepositMethod : ($branchDepositAmount > 0.0 ? $branchDepositMethod : 'cash');
    $_SESSION['booking_admin_payment_method'] = $adminDepositMethod;
    $_SESSION['booking_branch_payment_method'] = $branchDepositMethod;

    $bookingLandingPath = '../pages/job_details.php?id=' . $orderId;
    if ($depositAmount > 0.0) {
        $bookingLandingPath = '../pages/bill_receipt.php?id=' . $orderId
            . '&return_to=' . urlencode('job_details.php?id=' . $orderId);
    }

    redirect_with_flash(
        $bookingLandingPath,
        'success',
        localized_flash_message(
            'เปิดบิล {bill_number} พร้อมคิว #{queue_number} เรียบร้อยแล้ว',
            'Created booking {bill_number} with queue #{queue_number}.',
            array(
                'bill_number' => $billNumber,
                'queue_number' => $queueNumber,
            )
        )
    );
} catch (Throwable $exception) {
    $fallbackMessage = $exception->getMessage() === '__booking_total_must_be_positive__'
        ? $bookingTotalRequiredMessage
        : localized_flash_message(
            'เธเธฑเธเธ—เธถเธเธเธฒเธฃเน€เธเธดเธ”เธเธดเธฅเนเธกเนเธชเธณเน€เธฃเนเธ เธเธฃเธธเธ“เธฒเธ•เธฃเธงเธเธชเธญเธเธเนเธญเธกเธนเธฅเนเธฅเนเธงเธฅเธญเธเนเธซเธกเนเธญเธตเธเธเธฃเธฑเนเธ',
            'Booking was not saved. Please check the data and try again.'
        );
    process_handle_exception_and_redirect(
        $conn,
        $exception,
        'save_booking',
        '../pages/booking.php',
        $fallbackMessage,
        array(
            'branch_name' => $branchName,
            'service_ids' => $serviceIds,
            'customer_name' => $customerName,
            'plate_number' => $plateNumber,
            'admin_deposit_amount' => $adminDepositRaw,
            'branch_deposit_amount' => $branchDepositRaw,
        ),
        $oldInput,
        false,
        static function () use ($adminSlipFilePath, $branchSlipFilePath): void {
            if (!empty($adminSlipFilePath)) {
                delete_payment_slip_file($adminSlipFilePath);
            }
            if (!empty($branchSlipFilePath)) {
                delete_payment_slip_file($branchSlipFilePath);
            }
        }
    );
    return;
}
