<?php
declare(strict_types=1);

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

require_permission($conn, 'queue');
ensure_order_cancellation_columns($conn);
ensure_order_reopen_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_bill_number_column($conn);
ensure_order_detail_options_column($conn);

process_post_only_or_redirect('../pages/queue.php');

$orderId = filter_input(INPUT_POST, 'order_id', FILTER_VALIDATE_INT);
$status = trim((string) ($_POST['status'] ?? ''));
$returnTo = trim((string) ($_POST['return_to'] ?? ''));
$returnPath = process_safe_return_path($returnTo);
$statusRedirectPath = $returnPath !== '' ? $returnPath : '../pages/queue.php';
$allowedStatuses = array('waiting', 'washing', 'done', 'cancelled');

if (!$orderId || !in_array($status, $allowedStatuses, true)) {
    redirect_with_flash(
        '../pages/queue.php',
        'error',
        localized_flash_message('คำสั่งอัปเดตสถานะงานไม่ถูกต้อง', 'Invalid job status update.')
    );
}

ensure_post_csrf_or_redirect('queue_status', $statusRedirectPath);
ensure_submission_token_or_redirect(
    'queue_status_' . $orderId . '_' . $status,
    $statusRedirectPath
);

$conn->begin_transaction();

try {
    $order = process_load_order_context_or_redirect(
        $conn,
        $orderId,
        '../pages/queue.php',
        localized_flash_message('ไม่พบบิล', 'Order not found.'),
        array(
            'extra_columns' => array('o.order_source'),
            'branch_redirect_path' => '../pages/dashboard.php',
            'branch_message' => localized_flash_message('คุณไม่มีสิทธิ์เข้าถึงสาขานี้', 'Branch access denied.'),
        )
    );

    if ((string) ($order['order_source'] ?? 'booking') !== 'booking') {
        throw new RuntimeException(localized_flash_message_string(
            'บิล Quick Service ไม่ใช้การเปลี่ยนสถานะคิวแบบนี้',
            'Quick Service bills do not use queue status changes.'
        ));
    }

    $currentStatus = (string) ($order['status'] ?? '');
    $requiresBillControl = false;
    $requiresStartWaitingPermission = false;
    $isAllowedTransition = false;

    if ($status === 'cancelled') {
        throw new RuntimeException(localized_flash_message_string(
            'การยกเลิกบิลต้องระบุเหตุผลผ่านหน้าฟอร์มยกเลิก',
            'Cancellation requires a reason.'
        ));
    }

    if ($status === $currentStatus) {
        $isAllowedTransition = true;
    } elseif ($currentStatus === 'waiting' && $status === 'washing') {
        $isAllowedTransition = true;
        $requiresStartWaitingPermission = true;
    } elseif ($currentStatus === 'washing' && $status === 'done') {
        $isAllowedTransition = true;
    } elseif ($currentStatus === 'washing' && $status === 'waiting') {
        $isAllowedTransition = true;
        $requiresBillControl = true;
    } elseif ($currentStatus === 'done' && $status === 'washing') {
        $isAllowedTransition = true;
        $requiresBillControl = true;
    } elseif ($currentStatus === 'cancelled' && $status === 'waiting') {
        $isAllowedTransition = true;
        $requiresBillControl = true;
    }

    if (!$isAllowedTransition) {
        throw new RuntimeException(localized_flash_message_string(
            'ไม่สามารถเปลี่ยนสถานะงานตามลำดับนี้ได้',
            'Invalid status transition.'
        ));
    }

    if ($requiresStartWaitingPermission && !can_access($conn, 'queue_start_waiting')) {
        throw new RuntimeException(localized_flash_message_string(
            'คุณไม่มีสิทธิ์เริ่มงานจากสถานะรอดำเนินการ',
            'Start permission denied.'
        ));
    }

    if ($requiresBillControl && !can_access($conn, 'queue_bill_control')) {
        throw new RuntimeException(localized_flash_message_string(
            'คุณไม่มีสิทธิ์ย้อนสถานะหรือเปิดบิลใหม่',
            'Bill control permission denied.'
        ));
    }

    if ($currentStatus === 'cancelled' && $status === 'waiting') {
        $originalOrderStmt = db_prepare($conn, "
            SELECT
                order_id,
                customer_id,
                car_id,
                employee_id,
                branch_name,
                order_date,
                scheduled_at,
                slide_send_at,
                service_at,
                return_at,
                order_source,
                total_price,
                notes,
                bill_number
            FROM orders
            WHERE order_id = ?
            LIMIT 1
        ");
        $originalOrderStmt->bind_param('i', $orderId);
        $originalOrderStmt->execute();
        $originalOrder = $originalOrderStmt->get_result()->fetch_assoc();
        $originalOrderStmt->close();

        if (!$originalOrder) {
            throw new RuntimeException(localized_flash_message_string('ไม่พบบิลต้นทาง', 'Original order not found.'));
        }

        $currentUser = auth_current_user($conn);
        $employeeIdValue = (int) ($currentUser['employee_id'] ?? 0);
        $customerId = (int) ($originalOrder['customer_id'] ?? 0);
        $carId = (int) ($originalOrder['car_id'] ?? 0);
        $branchName = (string) ($originalOrder['branch_name'] ?? '');
        $orderDate = (string) ($originalOrder['order_date'] ?? date('Y-m-d H:i:s'));
        $scheduledAt = $originalOrder['scheduled_at'] ? (string) $originalOrder['scheduled_at'] : null;
        $slideSendAt = $originalOrder['slide_send_at'] ? (string) $originalOrder['slide_send_at'] : null;
        $serviceAt = $originalOrder['service_at'] ? (string) $originalOrder['service_at'] : null;
        $returnAt = $originalOrder['return_at'] ? (string) $originalOrder['return_at'] : null;
        $orderSource = (string) ($originalOrder['order_source'] ?? 'booking');
        $totalPrice = (float) ($originalOrder['total_price'] ?? 0);
        $notes = (string) ($originalOrder['notes'] ?? '');

        $insertOrderStmt = db_prepare($conn, "
            INSERT INTO orders (
                customer_id,
                car_id,
                employee_id,
                branch_name,
                order_date,
                scheduled_at,
                slide_send_at,
                service_at,
                return_at,
                order_source,
                status,
                payment_status,
                total_price,
                deposit_amount,
                deposit_method,
                notes,
                reopened_from_order_id
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'waiting', 'unpaid', ?, 0.00, NULL, ?, ?)
        ");
        $insertOrderStmt->bind_param(
            'iiisssssssdsi',
            $customerId,
            $carId,
            $employeeIdValue,
            $branchName,
            $orderDate,
            $scheduledAt,
            $slideSendAt,
            $serviceAt,
            $returnAt,
            $orderSource,
            $totalPrice,
            $notes,
            $orderId
        );
        $insertOrderStmt->execute();
        $newOrderId = (int) $conn->insert_id;
        $insertOrderStmt->close();

        $billNumber = build_order_bill_number($branchName, $orderDate, $newOrderId);
        $updateBillStmt = db_prepare($conn, "
            UPDATE orders
            SET bill_number = ?
            WHERE order_id = ?
        ");
        $updateBillStmt->bind_param('si', $billNumber, $newOrderId);
        $updateBillStmt->execute();
        $updateBillStmt->close();

        $details = array();
        $detailStmt = db_prepare($conn, "
            SELECT service_id, service_name_snapshot, price, duration_minutes, service_options
            FROM order_details
            WHERE order_id = ?
            ORDER BY detail_id ASC
        ");
        $detailStmt->bind_param('i', $orderId);
        $detailStmt->execute();
        $detailResult = $detailStmt->get_result();
        while ($detail = $detailResult->fetch_assoc()) {
            $details[] = $detail;
        }
        $detailStmt->close();

        $insertDetailStmt = db_prepare($conn, "
            INSERT INTO order_details (order_id, service_id, service_name_snapshot, price, duration_minutes, service_options)
            VALUES (?, ?, ?, ?, ?, ?)
        ");
        foreach ($details as $detail) {
            $serviceId = (int) ($detail['service_id'] ?? 0);
            $serviceName = (string) ($detail['service_name_snapshot'] ?? '');
            $price = (float) ($detail['price'] ?? 0);
            $durationMinutes = (int) ($detail['duration_minutes'] ?? 0);
            $serviceOptions = $detail['service_options'] !== null ? (string) $detail['service_options'] : null;
            $insertDetailStmt->bind_param('iisdis', $newOrderId, $serviceId, $serviceName, $price, $durationMinutes, $serviceOptions);
            $insertDetailStmt->execute();
        }
        $insertDetailStmt->close();

        $reopenedServiceRows = service_catalog_rows($conn, array(
            'service_ids' => array_values(array_unique(array_filter(array_map(static function (array $detail): int {
                return (int) ($detail['service_id'] ?? 0);
            }, $details)))),
        ));
        $reopenedServicesById = array();
        foreach ($reopenedServiceRows as $serviceRow) {
            $reopenedServicesById[(int) ($serviceRow['service_id'] ?? 0)] = $serviceRow;
        }

        foreach ($details as $detail) {
            $serviceId = (int) ($detail['service_id'] ?? 0);
            $serviceRow = $reopenedServicesById[$serviceId] ?? null;
            if (!$serviceRow) {
                continue;
            }

            if ((string) ($serviceRow['service_type'] ?? 'sub_service') === 'package') {
                service_create_customer_package_purchase($conn, $customerId, $carId, $newOrderId, $serviceRow);
            }

            $decodedOptions = json_decode((string) ($detail['service_options'] ?? ''), true);
            $packageUsage = is_array($decodedOptions['package_usage'] ?? null) ? $decodedOptions['package_usage'] : null;
            if (!$packageUsage) {
                continue;
            }

            $customerPackageItemId = (int) ($packageUsage['customer_package_item_id'] ?? 0);
            if ($customerPackageItemId <= 0) {
                continue;
            }

            $balanceRow = service_customer_package_balance_row($conn, $customerId, $customerPackageItemId, $carId);
            if (!$balanceRow || (int) ($balanceRow['sub_service_id'] ?? 0) !== $serviceId) {
                throw new RuntimeException(localized_flash_message_string(
                    'แพ็กเกจที่เคยใช้กับบิลเดิมไม่สามารถนำกลับมาใช้ได้แล้ว',
                    'Package balance is no longer available for reopened bill.'
                ));
            }

            service_apply_package_usage($conn, $newOrderId, $serviceRow, $balanceRow);
        }

        $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', $newOrderId, $queueNumber);
        $insertQueueStmt->execute();
        $insertQueueStmt->close();

        audit_log($conn, array(
            'module_key' => 'queue',
            'action_key' => 'reopen',
            'entity_type' => 'order',
            'entity_id' => $newOrderId,
            'order_id' => $newOrderId,
            'customer_id' => $customerId,
            'car_id' => $carId,
            'branch_name' => $branchName,
            'bill_number' => $billNumber,
            'amount' => $totalPrice,
            'message' => 'Cancelled bill reopened as a new queue bill.',
            'before' => array(
                'reopened_from_order_id' => $orderId,
                'reopened_from_bill_number' => (string) ($originalOrder['bill_number'] ?? ''),
                'status' => 'cancelled',
            ),
            'after' => array(
                'status' => 'waiting',
                'queue_number' => $queueNumber,
                'reopened_from_order_id' => $orderId,
            ),
        ));

        $oldBillLabel = (string) (($originalOrder['bill_number'] ?? '') !== '' ? $originalOrder['bill_number'] : '#' . $orderId);
        $reopenRedirectPath = $returnPath !== '' ? $returnPath : process_order_redirect_path($newOrderId);

        $conn->commit();
        redirect_with_flash(
            $reopenRedirectPath,
            'success',
            localized_flash_message(
                'เปิดใหม่จากบิล {old_bill} เป็น {new_bill} เรียบร้อยแล้ว',
                'Reopened {old_bill} as {new_bill}.',
                array(
                    'old_bill' => $oldBillLabel,
                    'new_bill' => $billNumber,
                )
            )
        );
    }

    if ($currentStatus === 'done' && $status !== 'done') {
        order_reverse_loyalty_earn_if_exists(
            $conn,
            $orderId,
            $order,
            'Loyalty points reversed because job left done status.'
        );
    }

    if ($status === 'washing') {
        $updateOrderStmt = db_prepare($conn, "
            UPDATE orders
            SET status = 'washing',
                started_at = COALESCE(started_at, NOW()),
                finished_at = NULL
            WHERE order_id = ?
        ");
        $updateQueueStmt = db_prepare($conn, "
            UPDATE queue
            SET status = 'washing',
                called_at = COALESCE(called_at, NOW()),
                completed_at = NULL
            WHERE order_id = ?
        ");
    } elseif ($status === 'done') {
        $updateOrderStmt = db_prepare($conn, "
            UPDATE orders
            SET status = 'done',
                finished_at = COALESCE(finished_at, NOW())
            WHERE order_id = ?
        ");
        $updateQueueStmt = db_prepare($conn, "
            UPDATE queue
            SET status = 'done',
                completed_at = COALESCE(completed_at, NOW())
            WHERE order_id = ?
        ");
    } else {
        $updateOrderStmt = db_prepare($conn, "
            UPDATE orders
            SET status = 'waiting',
                started_at = NULL,
                finished_at = NULL
            WHERE order_id = ?
        ");
        $updateQueueStmt = db_prepare($conn, "
            UPDATE queue
            SET status = 'waiting',
                called_at = NULL,
                completed_at = NULL
            WHERE order_id = ?
        ");
    }

    $updateOrderStmt->bind_param('i', $orderId);
    $updateOrderStmt->execute();
    $updateOrderStmt->close();

    $updateQueueStmt->bind_param('i', $orderId);
    $updateQueueStmt->execute();
    $updateQueueStmt->close();

    if ($status === 'done') {
        $points = max(1, (int) floor(((float) ($order['total_price'] ?? 0)) / 100));
        $customerId = (int) ($order['customer_id'] ?? 0);
        $description = "Earned from order #{$orderId}";

        $loyaltyStmt = db_prepare($conn, "
            INSERT IGNORE INTO loyalty_transactions (customer_id, order_id, points, transaction_type, description)
            VALUES (?, ?, ?, 'earn', ?)
        ");
        $loyaltyStmt->bind_param('iiis', $customerId, $orderId, $points, $description);
        $loyaltyStmt->execute();
        $inserted = $loyaltyStmt->affected_rows > 0;
        $loyaltyTransactionId = $inserted ? (int) $conn->insert_id : 0;
        $loyaltyStmt->close();

        if ($inserted) {
            $customerStmt = db_prepare($conn, "
                UPDATE customers
                SET loyalty_points = loyalty_points + ?
                WHERE customer_id = ?
            ");
            $customerStmt->bind_param('ii', $points, $customerId);
            $customerStmt->execute();
            $customerStmt->close();

            audit_log($conn, array(
                'module_key' => 'loyalty',
                'action_key' => 'earn',
                'entity_type' => 'loyalty_transaction',
                'entity_id' => $loyaltyTransactionId,
                'order_id' => $orderId,
                'customer_id' => $customerId,
                'car_id' => (int) ($order['car_id'] ?? 0),
                'branch_name' => (string) ($order['branch_name'] ?? ''),
                'bill_number' => (string) ($order['bill_number'] ?? ''),
                'message' => 'Loyalty points earned from completed job.',
                'after' => array(
                    'transaction_type' => 'earn',
                    'points' => $points,
                    'description' => $description,
                ),
            ));
        }
    }

    if ($status !== $currentStatus) {
        audit_log($conn, array(
            'module_key' => 'queue',
            'action_key' => 'status',
            'entity_type' => 'order',
            'entity_id' => $orderId,
            'order_id' => $orderId,
            'customer_id' => (int) ($order['customer_id'] ?? 0),
            'car_id' => (int) ($order['car_id'] ?? 0),
            'branch_name' => (string) ($order['branch_name'] ?? ''),
            'bill_number' => (string) ($order['bill_number'] ?? ''),
            'amount' => (float) ($order['total_price'] ?? 0),
            'message' => 'Queue status updated.',
            'before' => array(
                'status' => $currentStatus,
                'payment_status' => (string) ($order['payment_status'] ?? ''),
            ),
            'after' => array(
                'status' => $status,
                'payment_status' => (string) ($order['payment_status'] ?? ''),
            ),
        ));
    }

    $conn->commit();
    redirect_with_flash(
        $returnPath !== '' ? $returnPath : process_order_redirect_path($orderId),
        'success',
        localized_flash_message('อัปเดตสถานะงานเรียบร้อยแล้ว', 'Job status updated.')
    );
} catch (Throwable $exception) {
    process_handle_exception_and_redirect(
        $conn,
        $exception,
        'update_job_status',
        $statusRedirectPath,
        localized_flash_message('อัปเดตสถานะงานไม่สำเร็จ', 'Status update failed.'),
        array(
            'order_id' => $orderId,
            'requested_status' => $status,
            'return_to' => $returnTo,
        )
    );
}
