<?php

namespace routes;

use classes\authentication;
use classes\economic_v2_versioning_service;
use classes\security_policy_service;
use customers\economic_customer_mo;
use objects\bookings_o;
use objects\customer_vehicles_o;
use objects\departments_o;
use objects\logs_o;
use objects\order_bookings_o;
use objects\orders_o;
use objects\plate_scans_o;
use objects\products_o;
use objects\users_o;
use traits\route_t;
use modules\subusers\helpers\subusers_permission_node_key;

require_once WD . '/classes/security_policy_service.php';

class vehiclesRoute
{
    use route_t;

    private function routePositiveInt(string $name): int
    {
        global $response;

        $raw = $this->fromRoute($name);
        if (!is_string($raw) || !preg_match('/^[1-9][0-9]*$/', $raw)) {
            $response->error('Invalid route parameter', 400);
        }

        return (int)$raw;
    }

    private function resolveSuperuserVehicleTargetUser(int $userId): array
    {
        global $response;

        $targetUser = (new users_o())->select($userId);
        if (!$targetUser->exists()) {
            $response->error('User not found', 404);
        }
        $targetUser->getObjectProperties();

        $customerNumber = (int)$targetUser->customer_number->value();
        if ($customerNumber <= 0) {
            $response->error('Selected user does not have a customer number', 400);
        }

        return [
            'user_id' => (int)$targetUser->id,
            'customer_number' => $customerNumber,
            'customer_name' => (string)$targetUser->getCustomerName($customerNumber),
        ];
    }

    private function addUserScopedVehicleMeta(array $targetUser): void
    {
        global $response;

        $response->add_meta('user_context', $targetUser);
        $response->add_meta('vehicles_summary', $this->buildVehicleSummaryForCustomer((int)$targetUser['customer_number']));
    }

    private function buildVehiclePayload(array $vehicle): array
    {
        return [...(new customer_vehicles_o())->select((int)$vehicle['id'])->asArray()];
    }

    private function listVehiclesForCustomer(int $customerNumber): array
    {
        $vehicles = new customer_vehicles_o();

        return $vehicles->listObjectsWithPaginationIfSet(
            fn ($vehicle) => $this->buildVehiclePayload($vehicle),
            $vehicles->forceRestrictFilters([
                'customer_id' => [$customerNumber],
            ])
        );
    }

    private function buildVehicleSummaryForCustomer(int $customerNumber): array
    {
        $vehicles = new customer_vehicles_o();
        $filters = [
            'customer_id' => $customerNumber,
        ];
        if ($vehicles->columnsExist(['deleted_at'])) {
            $filters['deleted_at'] = null;
        }

        $rows = $vehicles->getFieldsWhere($filters, [
            'id',
            'wash_subscription',
        ]);

        $summary = [
            'total' => 0,
            'wash_subscription' => 0,
            'self_service' => 0,
        ];

        foreach ($rows as $row) {
            $summary['total']++;
            if ((int)($row['wash_subscription'] ?? 0) === 1) {
                $summary['wash_subscription']++;
            }

            try {
                $vehicle = (new customer_vehicles_o())->select((int)$row['id']);
                if ($vehicle->exists() && $vehicle->hasXLVask()) {
                    $summary['self_service']++;
                }
            } catch (\Throwable) {
                // XLVask availability should not prevent the customer vehicle summary from loading.
            }
        }

        return $summary;
    }

    private function requireScopedVehicle(int $vehicleId, int $customerNumber): customer_vehicles_o
    {
        global $response;

        $vehicle = (new customer_vehicles_o())->select($vehicleId);
        if (!$vehicle->exists()) {
            $response->error('Vehicle not found', 404);
        }
        $vehicle->getObjectProperties();
        if ((int)$vehicle->customer_id->value() !== $customerNumber) {
            $response->error('Vehicle does not belong to selected user', 404);
        }

        return $vehicle;
    }

    private function validateOptionalCustomerIdMatches(int $customerNumber): void
    {
        global $response;

        if (!self::isParametersSet(['customer_id'])) {
            return;
        }

        $requestedCustomerNumber = (int)self::getParameter('customer_id');
        self::requireType($requestedCustomerNumber, self::type_int());
        if ($requestedCustomerNumber !== $customerNumber) {
            $response->error('Customer number does not match selected user', 400);
        }
    }

    private function createVehicleForCustomer(int $customerNumber): array
    {
        global $response;

        self::requireParameters([
            'type',
            'reg',
        ]);
        $this->validateOptionalCustomerIdMatches($customerNumber);

        $reference = null;
        if (self::isParametersSet(['reference']) && !empty(self::getParameter('reference'))) {
            $reference = (string)self::getParameter('reference');
            self::requireType($reference, self::type_string());
            self::requireMinLength('reference', 1);
            self::requireMaxLength('reference', 255);
        }

        self::requireType(self::getParameter('reg'), self::type_string());
        self::requireType(self::getParameter('type'), self::type_int());
        $subscription = false;
        if (self::isParametersSet(['wash_subscription'])) {
            self::requireType(self::getParameter('wash_subscription'), self::type_bool());
            $subscription = (bool)self::getParameter('wash_subscription');
        }

        $reg = trim((string)self::getParameter('reg'));
        self::requireMinLength('reg', 2);
        self::requireMaxLength('reg', 12);
        $type = (int)self::getParameter('type');

        $vehicle = new customer_vehicles_o();
        $vehicle->add($customerNumber, $type, $reg, $subscription, $reference);

        try {
            (new economic_v2_versioning_service())->recordVehicleSubscriptionVersion(
                [
                    'vehicle_id' => (int)$vehicle->id,
                    'customer_number' => $customerNumber,
                    'reg' => $reg,
                    'vehicle_type' => $type,
                    'wash_subscription' => $subscription,
                ],
                date('Y-m-d H:i:s'),
                'live.vehicle.route',
                1.0,
                false,
                [
                    'route' => '/superuser/users/{user_id}/vehicles',
                    'method' => 'POST',
                    'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
                ]
            );
        } catch (\Throwable $exception) {
            (new logs_o())->add('vehicles', 'global', 0, (int)((new authentication())->get_user()->id ?? 0), 'ADD_VEHICLE_VERSIONING_FAILED', $exception->getMessage());
        }

        $this->observeVehicleCreated($customerNumber, (int)$vehicle->id, '/superuser/users/{user_id}/vehicles');

        return $vehicle->asArray();
    }

    private function observeVehicleCreated(int $customerNumber, int $vehicleId, string $route): void
    {
        try {
            (new security_policy_service())->observeVehicleCreated($customerNumber, [
                'vehicle_id' => $vehicleId,
                'route' => $route,
            ]);
        } catch (\Throwable) {
            // Security observation must not change vehicle creation responses.
        }
    }

    private function updateScopedVehicle(customer_vehicles_o $vehicle, int $customerNumber): array
    {
        global $response;

        $this->validateOptionalCustomerIdMatches($customerNumber);

        $beforeState = [
            'vehicle_id' => (int)$vehicle->id,
            'customer_number' => (int)$vehicle->customer_id->value(),
            'reg' => (string)$vehicle->reg->value(),
            'vehicle_type' => (int)$vehicle->type->value(),
            'wash_subscription' => (bool)$vehicle->wash_subscription->value(),
        ];

        if (self::isParametersSet(['type'])) {
            $type = (int)self::getParameter('type');
            self::requireType($type, self::type_int());
            self::requireMinValue($type, 0);
            if ($type === 0) {
                $vehicle->type->set(0);
                $vehicle->wash_subscription->set(0);
            } else {
                $product = new products_o();
                $product->select($type);
                if (!$product->exists() || !$product->subscription_allowed->value()) {
                    $response->error('Invalid type', 400);
                }
                $vehicle->type->set($type);
            }
        }

        if (self::isParametersSet(['reg'])) {
            $reg = (string)self::getParameter('reg');
            self::requireType($reg, self::type_string());
            self::requireMinLength('reg', 2);
            self::requireMaxLength('reg', 12);
            $vehicle->reg->set(preg_replace('/\s+/', '', $reg));
        }

        if (self::isParametersSet(['wash_subscription'])) {
            $subscription = (bool)self::getParameter('wash_subscription');
            self::requireType($subscription, self::type_bool());
            if ((int)$vehicle->type->value() === 0 && $subscription) {
                $response->error('Unable to set subscription, type is not set', 400);
            }
            $vehicle->wash_subscription->set($subscription ? 1 : 0);
        }

        if (self::isParametersSet(['reference'])) {
            if (empty(self::getParameter('reference'))) {
                $vehicle->reference->nullify();
            } else {
                $reference = (string)self::getParameter('reference');
                self::requireType($reference, self::type_string());
                self::requireMinLength('reference', 1);
                self::requireMaxLength('reference', 255);
                $vehicle->reference->set($reference);
            }
        }

        $vehicle->objectChanged();

        $afterState = [
            'vehicle_id' => (int)$vehicle->id,
            'customer_number' => (int)$vehicle->customer_id->value(),
            'reg' => (string)$vehicle->reg->value(),
            'vehicle_type' => (int)$vehicle->type->value(),
            'wash_subscription' => (bool)$vehicle->wash_subscription->value(),
        ];

        $versionRelevantChange = (
            (string)$beforeState['reg'] !== (string)$afterState['reg'] ||
            (int)$beforeState['vehicle_type'] !== (int)$afterState['vehicle_type'] ||
            (bool)$beforeState['wash_subscription'] !== (bool)$afterState['wash_subscription']
        );
        if ($versionRelevantChange) {
            try {
                $versioning = new economic_v2_versioning_service();
                $effectiveAt = date('Y-m-d H:i:s');
                if ((string)$beforeState['reg'] !== (string)$afterState['reg']) {
                    $versioning->closeActiveVehicleSubscriptionVersion(
                        (int)$beforeState['customer_number'],
                        (string)$beforeState['reg'],
                        $effectiveAt,
                        'live.vehicle.route',
                        1.0,
                        false,
                        [
                            'route' => '/superuser/users/{user_id}/vehicles',
                            'method' => 'PUT',
                            'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
                            'reason' => 'identity_change',
                        ]
                    );
                }
                $versioning->recordVehicleSubscriptionVersion(
                    $afterState,
                    $effectiveAt,
                    'live.vehicle.route',
                    1.0,
                    false,
                    [
                        'route' => '/superuser/users/{user_id}/vehicles',
                        'method' => 'PUT',
                        'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
                    ]
                );
            } catch (\Throwable $exception) {
                (new logs_o())->add('vehicles', 'global', 0, (int)((new authentication())->get_user()->id ?? 0), 'EDIT_VEHICLE_VERSIONING_FAILED', $exception->getMessage());
            }
        }

        return $vehicle->asArray();
    }

    public function run(): void
    {
        $this->get('/vehicles', function () {
            global $response;
            $auth = new authentication();
            $user = $auth->get_user();
            // Define permissions with subuser node linkage
            $permission_own = self::definePermission('list_own_vehicles', subusers_permission_node_key::VEHICLES_LIST);
            $permission_other = self::definePermission('list_vehicles_other');
            $has_permission_other = self::hasPermission($permission_other);

            // If a specific ID is requested, validate access against that vehicle's customer context
            if ($this->isParametersSet(['id'])) {
                $id = (int)$this->getParameter('id');
                $this->requireType($id, self::type_int());
                $this->requireMinValue($id, 1);
                $this->requireMaxValue($id, 9999999999);
                $vehicle = (new customer_vehicles_o())->select($id);
                if (!$vehicle->exists()) {
                    (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'LIST_OWN_VEHICLES', 'Vehicle not found');
                    $response->error('Vehicle not found', 404);
                }
                $targetCustomer = (int)$vehicle->customer_id->value();
                self::allowOwnOrDepartmentAccess(
                    $permission_own,
                    $permission_other,
                    $targetCustomer,
                    null,
                    null,
                    'You do not have permission to view this vehicle.'
                );
                $response->success([...$vehicle->asArray()]);
            }

            // Listing: restrict to effective customer when lacking the broader permission
            $vehicles_o = new customer_vehicles_o();
            $effectiveCustomer = self::resolveEffectiveCustomerNumber();
            $permitted = self::allowOwnOrDepartmentAccess(
                $permission_own,
                $permission_other,
                $effectiveCustomer,
                null,
                null,
                'You do not have permission to view vehicles.'
            );
            if (!$permitted) {
                $response->error('You do not have permission to view vehicles.');
            }
            $response->success(
                $vehicles_o->listObjectsWithPaginationIfSet(
                    function ($vehicle) {
                        return [...(new customer_vehicles_o())->select($vehicle['id'])->asArray()];
                    },
                    $vehicles_o->forceRestrictFilters([
                        ...(!$has_permission_other && $effectiveCustomer !== null ? [
                            'customer_id' => [(int)$effectiveCustomer]
                        ] : [])
                    ])
                )
            );
        },
            [
                'list_own_vehicles' => 'List own vehicles',
                'list_vehicles_other' => 'List other users vehicles',
            ]
        );

        $this->get('/department/vehicles/unknown-customer', function () {
            // Require the user to be logged in
            global $response;
            $this->requirePermission('list_unknown_customer_vehicles');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Log the incident
                (new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_UNKNOWN_CUSTOMER_VEHICLES', 'Successfully listed unknown customer vehicles');
                // Return the list of the user's vehicles
                $vehicles_o = new orders_o();
                $vehicles_o->setView('unique_order_reg_1');
                $response->success($vehicles_o
                    ->setSearchableFields([
                        'reg_1'
                    ])
                    ->listObjectsWithPaginationIfSet()
                );
            } else {
                // Log the incident
                (new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_UNKNOWN_CUSTOMER_VEHICLES', 'No user found, or invalid session');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'list_unknown_customer_vehicles' => 'List unknown customer vehicles',
            ]
        );

        $this->get('/superuser/users/{user_id}/vehicles', function () {
            global $response;
            $this->requirePermission('list_vehicles_other');
            $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
            $this->addUserScopedVehicleMeta($targetUser);
            $response->success($this->listVehiclesForCustomer((int)$targetUser['customer_number']));
        }, [
            'list_vehicles_other' => 'List vehicles for a selected superuser customer account.',
        ]);

        $this->get('/superuser/users/{user_id}/vehicles/summary', function () {
            global $response;
            $this->requirePermission('list_vehicles_other');
            $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
            $response->add_meta('user_context', $targetUser);
            $response->success($this->buildVehicleSummaryForCustomer((int)$targetUser['customer_number']));
        }, [
            'list_vehicles_other' => 'Summarize vehicles for a selected superuser customer account.',
        ]);

        $this->post('/superuser/users/{user_id}/vehicles', function () {
            global $response;
            $this->requirePermission('add_vehicle_other');
            $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
            $response->add_meta('user_context', $targetUser);
            $response->success($this->createVehicleForCustomer((int)$targetUser['customer_number']));
        }, [
            'add_vehicle_other' => 'Add a vehicle for a selected superuser customer account.',
        ]);

        $this->put('/superuser/users/{user_id}/vehicles', function () {
            global $response;
            $this->requirePermission('edit_vehicle_other');
            self::requireParameters(['id']);
            $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
            $vehicleId = (int)self::getParameter('id');
            self::requireType($vehicleId, self::type_int());
            self::requireMinValue($vehicleId, 1);
            $vehicle = $this->requireScopedVehicle($vehicleId, (int)$targetUser['customer_number']);
            $response->add_meta('user_context', $targetUser);
            $response->success($this->updateScopedVehicle($vehicle, (int)$targetUser['customer_number']));
        }, [
            'edit_vehicle_other' => 'Edit a vehicle for a selected superuser customer account.',
        ]);

        $this->delete('/superuser/users/{user_id}/vehicles', function () {
            global $response;
            $this->requirePermission('delete_vehicle_other');
            self::requireParameters(['id']);
            $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
            $vehicleId = (int)self::getParameter('id');
            self::requireType($vehicleId, self::type_int());
            self::requireMinValue($vehicleId, 1);
            $vehicle = $this->requireScopedVehicle($vehicleId, (int)$targetUser['customer_number']);

            $beforeState = [
                'customer_number' => (int)$vehicle->customer_id->value(),
                'reg' => (string)$vehicle->reg->value(),
            ];
            $vehicle->delete();
            try {
                (new economic_v2_versioning_service())->closeActiveVehicleSubscriptionVersion(
                    (int)$beforeState['customer_number'],
                    (string)$beforeState['reg'],
                    date('Y-m-d H:i:s'),
                    'live.vehicle.route',
                    1.0,
                    false,
                    [
                        'route' => '/superuser/users/{user_id}/vehicles',
                        'method' => 'DELETE',
                        'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
                    ]
                );
            } catch (\Throwable $exception) {
                (new logs_o())->add('vehicles', 'global', 0, (int)((new authentication())->get_user()->id ?? 0), 'DELETE_VEHICLE_VERSIONING_FAILED', $exception->getMessage());
            }

            $response->add_meta('user_context', $targetUser);
            $response->success([
                'success' => true,
                'message' => 'Vehicle deleted successfully',
            ]);
        }, [
            'delete_vehicle_other' => 'Delete a vehicle for a selected superuser customer account.',
        ]);

        $this->post('/vehicles', function () {
            global $response;
            $auth = new authentication();
            $user = $auth->get_user();

            // Define permissions with subuser node linkage
            $permission_own = self::definePermission('add_vehicle', subusers_permission_node_key::VEHICLES_ADD);
            $permission_other = self::definePermission('add_vehicle_other');

            // Require the parameters
            self::requireParameters([
                'type',
                'reg',
            ]);

            // Determine target customer
            $targetCustomer = self::isParametersSet(['customer_id'])
                ? (int)self::getParameter('customer_id')
                : (int)(self::resolveEffectiveCustomerNumber() ?? 0);
            self::requireType($targetCustomer, self::type_int());
            self::requireMinValue($targetCustomer, 1);
            self::requireMaxValue($targetCustomer, 9999999999);

            // Enforce access (own vs broader)
            self::allowOwnOrDepartmentAccess(
                $permission_own,
                $permission_other,
                $targetCustomer,
                null,
                null,
                'You are not allowed to add vehicles to this customer'
            );

            $reference = null;
            if (self::isParametersSet(['reference'])) {
                if (!empty(self::getParameter('reference'))) {
                    $reference = (string)self::getParameter('reference');
                    self::requireType($reference, self::type_string());
                    self::requireMinLength('reference', 1);
                    self::requireMaxLength('reference', 255);
                }
            }
            // Validate the parameters
            self::requireType(self::getParameter('reg'), self::type_string());
            self::requireType(self::getParameter('type'), self::type_int());
            $subscription = false;
            if (self::isParametersSet(['wash_subscription'])) {
                self::requireType(self::getParameter('wash_subscription'), self::type_bool());
                $subscription = (bool)self::getParameter('wash_subscription');
            }
            // Get the parameters
            $reg = (string)self::getParameter('reg');
            $type = (int)self::getParameter('type');
            $reg = trim($reg);

            // Create a new vehicle
            $vehicle = new customer_vehicles_o();
            $vehicle->add(
                $targetCustomer,
                $type,
                $reg,
                $subscription ? 1 : 0,
                $reference
            );
            try {
                (new economic_v2_versioning_service())->recordVehicleSubscriptionVersion(
                    [
                        'vehicle_id' => (int)$vehicle->id,
                        'customer_number' => (int)$targetCustomer,
                        'reg' => (string)$reg,
                        'vehicle_type' => (int)$type,
                        'wash_subscription' => (bool)$subscription,
                    ],
                    date('Y-m-d H:i:s'),
                    'live.vehicle.route',
                    1.0,
                    false,
                    [
                        'route' => '/vehicles',
                        'method' => 'POST',
                        'actor_user_id' => (int)($user->id ?? 0),
                    ]
                );
            } catch (\Throwable $e) {
                (new logs_o())->add('vehicles', 'global', 0, (int)($user->id ?? 0), 'ADD_VEHICLE_VERSIONING_FAILED', $e->getMessage());
            }

            (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'ADD_VEHICLE', 'Successfully added vehicle');
            $this->observeVehicleCreated((int)$targetCustomer, (int)$vehicle->id, '/vehicles');
            $response->success($vehicle->asArray());
        },
            [
                'add_vehicle' => 'Add a vehicle to own vehicles',
                'add_vehicle_other' => 'Add a vehicle to another users vehicles',
            ]
        );

        $this->put('/vehicles', function () {
            global $response;
            $auth = new authentication();
            $user = $auth->get_user();
            $permission_own = self::definePermission('edit_vehicle', subusers_permission_node_key::VEHICLES_EDIT);
            $permission_other = self::definePermission('edit_vehicle_other');
            // Get the request data
            self::requireParameters(['id']);
            $id = (int)self::getParameter('id');
            $vehicle = (new customer_vehicles_o())->select($id);
            if (!$vehicle->exists()) {
                (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'EDIT_VEHICLE', 'Vehicle not found');
                $response->error('Vehicle not found', 404);
            }

            $before_state = [
                'vehicle_id' => (int)$vehicle->id,
                'customer_number' => (int)$vehicle->customer_id->value(),
                'reg' => (string)$vehicle->reg->value(),
                'vehicle_type' => (int)$vehicle->type->value(),
                'wash_subscription' => (bool)$vehicle->wash_subscription->value(),
            ];
            // Enforce access (own vs broader)
            self::allowOwnOrDepartmentAccess(
                $permission_own,
                $permission_other,
                (int)$vehicle->customer_id->value(),
                null,
                null,
                'You are not allowed to edit vehicles from other users'
            );
                if (self::isParametersSet(['customer_id'])) {
                    $new_customer_number = (int)self::getParameter('customer_id');
                    self::requireType($new_customer_number, self::type_int());
                    self::requireMinValue($new_customer_number, 1);
                    self::requireMaxValue($new_customer_number, 9999999999);
                    // Require access for the destination customer context as well.
                    self::allowOwnOrDepartmentAccess(
                        $permission_own,
                        $permission_other,
                        $new_customer_number,
                        null,
                        null,
                        'You are not allowed to move vehicles to this customer'
                    );
                    $vehicle->customer_id->set($new_customer_number);
                }
                // Check all the fields, and if they are set, validate and set them
                if (self::isParametersSet(['type'])) {
                    $type = (int)self::getParameter('type');
                    // Make sure the type is an integer
                    self::requireType($type, self::type_int());
                    self::requireMinValue($type, 0);
                    // Make sure the type is a valid type (If it isn't 0)
                    if ($type === 0) {
                        // Set the type to null
                        $vehicle->type->set(0);
                        // Turn off the subscription
                        $vehicle->wash_subscription->set(0);
                    } else {
                        $products_o = new products_o();
                        $products_o->select((int)$type);
                        if (!$products_o->exists() || !$products_o->subscription_allowed->value()) {
                            // Log the incident
                            (new logs_o())->add('vehicles', 'global', 1, $user->id, 'EDIT_VEHICLE', 'Invalid type');
                            // Return an error
                            $response->error('Invalid type', 400);
                        }
                        // Set the type
                        $vehicle->type->set(
                            (int)$type
                        );
                        // TODO: Make the potential vehicleTypeId reflect the correct type.
                        // It's currently possible to set the vehicleTypeId to a type that is not allowed for the vehicle.
                    }
                }
                if (self::isParametersSet(['reg'])) {
                    $reg = (string)self::getParameter('reg');
                    self::requireType($reg, self::type_string());
                    self::requireMinLength('reg', 2);
                    self::requireMaxLength('reg', 12);
                    // Strip the registration number of whitespace
                    $reg = preg_replace('/\s+/', '', $reg);
                    // Set the registration number
                    $vehicle->reg->set($reg);
                }
                if (self::isParametersSet(['wash_subscription'])) {
                    $subscription = (bool)self::getParameter('wash_subscription');
                    self::requireType($subscription, self::type_bool());
                    // Check if the type is a valid type (If it isn't 0)
                    if ((int)$vehicle->type->value() === 0 && $subscription) {
                        // Set the subscription to null
                        $response->error('Unable to set subscription, type is not set', 400);
                    }
                    // Set the wash subscription
                    $vehicle->wash_subscription->set($subscription ? 1 : 0);
                }
                if (self::isParametersSet(['reference'])) {
                    // Check if the reference is set to null/empty
                    if (empty(self::getParameter('reference'))) {
                        // Set the reference to null
                        $vehicle->reference->nullify();
                    } else {
                        // Check if the reference is valid
                        $reference = (string)self::getParameter('reference');
                        self::requireType($reference, self::type_string());
                        self::requireMinLength('reference', 1);
                        self::requireMaxLength('reference', 255);
                        // Set the reference
                        $vehicle->reference->set($reference);
                    }
                }
                $vehicle->objectChanged();

                $after_state = [
                    'vehicle_id' => (int)$vehicle->id,
                    'customer_number' => (int)$vehicle->customer_id->value(),
                    'reg' => (string)$vehicle->reg->value(),
                    'vehicle_type' => (int)$vehicle->type->value(),
                    'wash_subscription' => (bool)$vehicle->wash_subscription->value(),
                ];

                $version_relevant_change = (
                    (int)$before_state['customer_number'] !== (int)$after_state['customer_number'] ||
                    (string)$before_state['reg'] !== (string)$after_state['reg'] ||
                    (int)$before_state['vehicle_type'] !== (int)$after_state['vehicle_type'] ||
                    (bool)$before_state['wash_subscription'] !== (bool)$after_state['wash_subscription']
                );
                if ($version_relevant_change) {
                    try {
                        $versioning = new economic_v2_versioning_service();
                        $effective_at = date('Y-m-d H:i:s');
                        $identity_changed = (
                            (int)$before_state['customer_number'] !== (int)$after_state['customer_number'] ||
                            (string)$before_state['reg'] !== (string)$after_state['reg']
                        );
                        if ($identity_changed) {
                            $versioning->closeActiveVehicleSubscriptionVersion(
                                (int)$before_state['customer_number'],
                                (string)$before_state['reg'],
                                $effective_at,
                                'live.vehicle.route',
                                1.0,
                                false,
                                [
                                    'route' => '/vehicles',
                                    'method' => 'PUT',
                                    'actor_user_id' => (int)($user->id ?? 0),
                                    'reason' => 'identity_change',
                                ]
                            );
                        }
                        $versioning->recordVehicleSubscriptionVersion(
                            $after_state,
                            $effective_at,
                            'live.vehicle.route',
                            1.0,
                            false,
                            [
                                'route' => '/vehicles',
                                'method' => 'PUT',
                                'actor_user_id' => (int)($user->id ?? 0),
                            ]
                        );
                    } catch (\Throwable $e) {
                        (new logs_o())->add('vehicles', 'global', 0, (int)($user->id ?? 0), 'EDIT_VEHICLE_VERSIONING_FAILED', $e->getMessage());
                    }
                }
                // Return the vehicle
                $response->success($vehicle->asArray());
        },
            [
                'edit_vehicle' => 'Edit a vehicle',
                'edit_vehicle_other' => 'Edit a vehicle from another user'
            ]
        );

        $this->delete('/vehicles', function () {
            global $response;
            $auth = new authentication();
            $user = $auth->get_user();
            $permission_own = self::definePermission('delete_vehicle', subusers_permission_node_key::VEHICLES_DELETE);
            $permission_other = self::definePermission('delete_vehicle_other');
            // Get the request data
            self::requireParameters(['id']);
            $id = (int)self::getParameter('id');
            $vehicle = (new customer_vehicles_o())->select($id);
            if (!$vehicle->exists()) {
                (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'DELETE_VEHICLE', 'Vehicle not found');
                $response->error('Vehicle not found', 404);
            }

            $before_state = [
                'customer_number' => (int)$vehicle->customer_id->value(),
                'reg' => (string)$vehicle->reg->value(),
            ];
            // Enforce access (own vs broader)
            self::allowOwnOrDepartmentAccess(
                $permission_own,
                $permission_other,
                (int)$vehicle->customer_id->value(),
                null,
                null,
                'You are not allowed to delete vehicles from other users'
            );
            // Delete the vehicle
            $vehicle->delete();
            try {
                (new economic_v2_versioning_service())->closeActiveVehicleSubscriptionVersion(
                    (int)$before_state['customer_number'],
                    (string)$before_state['reg'],
                    date('Y-m-d H:i:s'),
                    'live.vehicle.route',
                    1.0,
                    false,
                    [
                        'route' => '/vehicles',
                        'method' => 'DELETE',
                        'actor_user_id' => (int)($user->id ?? 0),
                    ]
                );
            } catch (\Throwable $e) {
                (new logs_o())->add('vehicles', 'global', 0, (int)($user->id ?? 0), 'DELETE_VEHICLE_VERSIONING_FAILED', $e->getMessage());
            }
            $response->success([
                'success' => true,
                'message' => 'Vehicle deleted successfully'
            ]);
        },
            [
                'delete_vehicle' => 'Delete a vehicle',
                'delete_vehicle_other' => 'Remove (delete) a vehicle from another user'
            ]
        );

        $this->get('/department/vehicle/customer-suggestions', function () {
            // Require the user to be logged in
            global $response;
            $this->requirePermission('list_vehicle_customer_suggestions');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                self::requireParameters(['reg_1']);
                self::requireType((string)self::getParameter('reg_1'), self::type_string());
                $plate = (string)self::getParameter('reg_1');
                self::requireMinLength('reg_1', 1);
                self::requireMaxLength('reg_1', 12);
                // Return the list of the user's vehicles
                $orders_o = new orders_o();
                // Check if the user is allowed to list other user's vehicles
                // Get all the customer_ids that has orders with the same plate
                $customer_ids = $orders_o->getFieldsWhere([
                    'reg_1' => $plate,
                    'deleted_at' => null,
                ], [
                    'customer_id',
                ]);
                // Get all the customer_ids that has orders with the same plate
                $customer_ids = array_map(function ($customer_id) {
                    return (int)$customer_id['customer_id'];
                }, $customer_ids);
                // Remove duplicates
                $customer_ids = array_unique($customer_ids);
                // Get all the customers that has orders with the same plate
                $customers = [];
                foreach ( $customer_ids as $customer_id ) {
                    $customers[] = (new users_o())->getUserByCustomerNumber($customer_id);
                }
                // Log the incident
                (new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_VEHICLE_CUSTOMER_SUGGESTIONS', 'Successfully listed customer suggestions for a vehicle');
                // Format the response
                $response->success(
                    array_map(function ($customer) {
                        /** @var users_o $customer */
                        $customer_number = (int)$customer->customer_number->value();
                        return [
                            'id' => (int)$customer->id,
                            'customer_name' => (string)$customer->getCustomerName((int)$customer_number),
                            'customer_number' => (int)$customer_number,
                            'barred' => (new economic_customer_mo())->getCustomerByCustomerNumber((int)$customer_number)->asArray()['barred'],
                        ];
                    }, $customers)
                );

            } else {
                // Log the incident
                (new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_VEHICLE_CUSTOMER_SUGGESTIONS', 'No user found, or invalid session');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'list_vehicle_customer_suggestions' => 'List customer suggestions for a vehicle',
            ]
        );

        $this->post('/vehicles/set-auto-start-on-lpr', function () {
            global $response;
            $auth = new authentication();
            $user = $auth->get_user();
            $permission_own = self::definePermission('set_auto_start_on_lpr', subusers_permission_node_key::VEHICLES_EDIT);
            $permission_other = self::definePermission('set_auto_start_on_lpr_other');
            self::requireParameters(['id', 'active']);
            $id = (int)self::getParameter('id');
            self::requireType($id, self::type_int());
            self::requireMinValue($id, 1);
            self::requireMaxValue($id, 9999999999);
            $autoStartOnLpr = (bool)self::getParameter('active');
            self::requireType($autoStartOnLpr, self::type_bool());
            $vehicle = (new customer_vehicles_o())->select($id);
            if (!$vehicle->exists()) {
                (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_AUTO_START_ON_LPR', 'Vehicle not found');
                $response->error('Vehicle not found', 404);
            }
            self::allowOwnOrDepartmentAccess(
                $permission_own,
                $permission_other,
                (int)$vehicle->customer_id->value(),
                null,
                null,
                'You are not allowed to edit vehicles from other users'
            );
            $vehicle->setAutoStartOnLpr($autoStartOnLpr);
            $response->success([...$vehicle->asArray()]);
        },
            [
                'set_auto_start_on_lpr' => 'Set auto start on LPR',
                'set_auto_start_on_lpr_other' => 'Set auto start on LPR for another user\'s vehicle',
            ]
        );

        $this->post('/vehicles/set-vehicle-type-id', function () {
            global $response;
            $auth = new authentication();
            $user = $auth->get_user();
            $permission_own = self::definePermission('set_vehicle_type_id', subusers_permission_node_key::VEHICLES_EDIT);
            $permission_other = self::definePermission('set_vehicle_type_id_other');
            self::requireParameters(['id', 'vehicleTypeId']);
            $id = (int)self::getParameter('id');
            self::requireType($id, self::type_int());
            self::requireMinValue($id, 1);
            self::requireMaxValue($id, 9999999999);
            $vehicleTypeId = (string)self::getParameter('vehicleTypeId');
            self::requireType($vehicleTypeId, self::type_string());
            self::requireMinLength('vehicleTypeId', 1);
            self::requireMaxLength('vehicleTypeId', 50);
            $vehicle = (new customer_vehicles_o())->select($id);
            if (!$vehicle->exists()) {
                (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_VEHICLE_TYPE_ID', 'Vehicle not found');
                $response->error('Vehicle not found', 404);
            }
            self::allowOwnOrDepartmentAccess(
                $permission_own,
                $permission_other,
                (int)$vehicle->customer_id->value(),
                null,
                null,
                'You are not allowed to edit vehicles from other users'
            );
            $customer = (new users_o())->getUserByCustomerNumber((int)$vehicle->customer_id->value());
            if (!$vehicle->hasXLVask()) {
                if (!$customer->hasXLVaskCustomerAccount()) {
                    (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_VEHICLE_TYPE_ID', 'Attempt to set vehicle type ID without XL Vask registration and no customer account');
                    $response->error('Vehicle is not registered in the XL Vask system', 400);
                } else {
                    $vehicle->createXLVaskVehicle($vehicleTypeId);
                }
            } else {
                $vehicle->setVehicleTypeId($vehicleTypeId);
            }
            $response->success([...$vehicle->asArray()]);
        },
            [
                'set_vehicle_type_id' => 'Set vehicle type ID',
                'set_vehicle_type_id_other' => 'Set vehicle type ID for another user\'s vehicle',
            ]
        );


        $this->get('/superuser/users-with-vehicle-subscriptions', function () {
            // Require the user to be logged in
            global $response;
            $this->requirePermission('list_users_with_vehicle_subscriptions');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Log the incident
                (new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_USERS_WITH_VEHICLE_SUBSCRIPTIONS', 'Successfully listed users with vehicle subscriptions');
                // Return the list of users with vehicle subscriptions
                $vehicles_o = new customer_vehicles_o();
                $customer_ids = $vehicles_o->getFieldsWhere(
                    [
                        'wash_subscription' => 1,
                    ],
                    ['customer_id']
                );
                // Get all the customer_ids that has vehicle subscriptions
                $customer_ids = array_map(function ($customer_id) {
                    return (int)$customer_id['customer_id'];
                }, $customer_ids);
                // Remove duplicates
                $customer_ids = array_unique($customer_ids);
                $response->success(
                    $customer_ids ? array_map(function ($customer_id) {
                        $tmp_user = new users_o();
                        $tmp_user->getUserByCustomerNumber((int)$customer_id);
                        return [...$tmp_user->asArray()];
                    }, $customer_ids) : []
                );
            } else {
                // Log the incident
                (new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_USERS_WITH_VEHICLE_SUBSCRIPTIONS', 'No user found, or invalid session');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'list_users_with_vehicle_subscriptions' => 'List users with vehicle subscriptions',
            ]
        );

        $this->get('/vehicles/status', function () {
            // Require the user to be logged in
            global $response;
            $this->requirePermission('view_vehicle_status');
            /** Define parameters */
            // "reg" - The registration number to check the status of
            self::requireParameters([
                'reg',
            ]);
            self::requireType((string)self::getParameter('reg'), self::type_string());
            $reg = (string)self::getParameter('reg');
            self::requireMinLength('reg', 1);
            self::requireMaxLength('reg', 12);
            // "department" - The department to check the status of (optional)
            if (self::isParametersSet(['department'])) {
                self::requireType((int)self::getParameter('department'), self::type_int());
                $department = (int)self::getParameter('department');
                self::requireMinValue($department, 1);
                self::requireMaxValue($department, 999999);
                // Check if the department exists
                $department_o = new departments_o();
                $department_o->select($department);
                if (!$department_o->exists()) {
                    $response->error('Department not found', 404);
                }
            } else {
                $department = null;
            }
            // Validate the registration number
            $this->validateRegistrationNumber($reg, $response);
            $customer_vehicles_o = new customer_vehicles_o();
            /** Determine the statuses of the vehicle */
            $isVerified = $customer_vehicles_o->countRowsWhere(
                [
                    'reg' => $reg,
                ]
            ) > 0;
            $isKnown = (new orders_o())->countRowsWhere(
                [
                    'reg_1' => $reg,
                ]
            ) > 1; // We need to go above one, as the current order will also be counted
            $isBooked = (new bookings_o())->countRowsWhere(
                [
                    'regNrTraekker' => $reg,
                    'status' => 'pending',
                    ...($department ? ['department' => $department] : []), // Filter by department if set
                ]
            ) > 0;
            // Check if the customer is barred (only if the vehicle is verified)
            $customer_number = $isVerified ? ($customer_vehicles_o->getFieldsWhere(
                [
                    'reg' => $reg,
                ],
                ['customer_id']
            )) : null;
            $customer_number = $customer_number ? (int)$customer_number[0]['customer_id'] : null;
            $cardPaymentRequired = ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number));
            $last_order = $customer_vehicles_o->getLastOrderByPlate($reg);
            $response->success([
                'department' => $department,
                'reg' => $reg,
                'verified' => $isVerified,
                'known' => $isKnown,
                'booked' => $isBooked,
                'card' => $cardPaymentRequired,
                'unknown' => !$customer_number && !$isKnown && !$isBooked,
                'last_order_id' => $last_order?->id ?? null,
            ]);
        },
            [
                'view_vehicle_status' => 'View vehicle status (Verified, Known, Unknown, Booked, Card payment required, etc.)',
            ]
        );

        $this->get('/vehicles/search', function () {
            global $response;
            $this->requirePermission('search_vehicles');
            /**
             * This endpoint allows searching for vehicles by registration number.
             * This includes:
             * - verified vehicles (in customer_vehicles)
             * - known vehicles (in orders).
             * - booked vehicles (in bookings).
             * - unknown vehicles (not in any of the above).
             */
            self::requireParameters(['search']);
            self::requireType((string)self::getParameter('search'), self::type_string());
            $search = (string)self::getParameter('search');
            self::requireMinLength('search', 1);
            self::requireMaxLength('search', 12);
            $department = null;
            // If department is set, validate it
            if (self::isParametersSet(['department'])) {
                $department = (int)self::getParameter('department');
                self::requireType($department, self::type_int());
                self::requireMinValue($department, 1);
                self::requireMaxValue($department, 999999);
                // Check if the department exists
                $department_o = new departments_o();
                $department_o->select($department);
                if (!$department_o->exists()) {
                    $response->error('Department not found', 404);
                }
                self::requireDepartmentAccess($department);
            }
            // Strip the search term of whitespace
            $search = preg_replace('/\s+/', '', $search);
            /** Get the different lists of registration numbers */
            $options = ['limit' => 10];
            $booked_filters = [...($department ? ['department' => $department] : [])];
            $booking_lookup_filters = ['order_id' => null, 'deleted_at' => null, ...$booked_filters];
            $verified_regs = self::getVerifiedRegs($search, $options);
            $known_regs = self::getKnownRegs($search, $options);
            $booked_regs = self::getBookedRegs($search, $options, $booked_filters);
            $unknown_regs = array_filter(array_unique(array_merge([...self::getUnknownRegs($search, $options), $search]))); // Add the search term as the first item, to ensure it is included in the results
            /**
             * Prevent overlaps between the lists
             * - Booked vehicles should not be in known, unknown or verified
             * - Verified vehicles should not be in known or booked
             * - Known vehicles should not be in unknown
             * - Unknown vehicles should not be in any other list
             */
            $known_regs = array_filter($known_regs, function ($reg) use ($verified_regs, $booked_regs) {
                return !in_array($reg, $verified_regs) && !in_array($reg, $booked_regs);
            });
            $verified_regs = array_filter($verified_regs, function ($reg) use ($booked_regs) {
                return !in_array($reg, $booked_regs);
            });
            $unknown_regs = array_filter($unknown_regs, function ($reg) use ($verified_regs, $known_regs, $booked_regs) {
                return !in_array($reg, $verified_regs) && !in_array($reg, $known_regs) && !in_array($reg, $booked_regs);
            });
            // Sort the lists alphabetically
            sort($verified_regs);
            sort($known_regs);
            sort($booked_regs);
            sort($unknown_regs);
            // Define the custom relevance sorting function
            $relevant = [
                ...array_values(array_unique($booked_regs)),
                ...array_values(array_unique($verified_regs)),
                ...array_values(array_unique($known_regs)),
                ...array_values(array_unique($unknown_regs)),
            ];
            // Limit the results to 10 items total, while keeping the relevance order
            $vehicles = array_slice($relevant, 0, 10);
            /** Format the relevance results */
            $vehicles = array_map(function ($reg) use ($verified_regs, $known_regs, $booked_regs, $unknown_regs, $booking_lookup_filters) {
                // Determine the status of the vehicle
                $status = null;
                $isVerified = in_array($reg, $verified_regs);
                $isKnown = in_array($reg, $known_regs);
                $isBooked = in_array($reg, $booked_regs);
                $isUnknown = in_array($reg, $unknown_regs);
                // Initialize other variables
                $customer_number = null; // Will be populated for verified / booked vehicles
                $customer_name = null; // Will be populated for verified / booked vehicles
                $last_order_id = (new customer_vehicles_o())->getLastOrderByPlate($reg)?->id ?? null;
                // These variables are only for verified vehicles
                $type = null;
                $id = null;
                $reference = null;
                // Booking related variables
                $booking_id = null;
                $notes = null; // Booking notes
                $booking_datetime = null;

                // Set the status based on priority: booked > verified > known > unknown
                if ($isBooked) {
                    $status = 'booked';
                    $booking = self::getPendingOrderBookingSummaryByPlate($reg, $booking_lookup_filters);
                    // Populate other variables
                    $booking_id = $booking ? (int)$booking['id'] : null;
                    $customer_number = $booking ? (int)$booking['customer_number'] : null;
                    $reference = $booking ? (string)$booking['reference'] : null;
                    $notes = $booking ? (string)$booking['note'] : null;
                    $booking_datetime = $booking ? (string)($booking['datetime'] ?? '') : null;
                    // If the booking has a customer number, check if the customer is barred
                    if ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number)) {
                        $status = 'card';
                    }
                } elseif ($isVerified) {
                    $status = 'verified';
                    // Get the vehicle
                    $customer_vehicles_o = new customer_vehicles_o();
                    $vehicle = $customer_vehicles_o->getFieldsWhere(['reg' => $reg], ['customer_id', 'type', 'id', 'reference']);
                    // Populate other variables
                    $customer_number = $vehicle ? (int)$vehicle[0]['customer_id'] : null;
                    $type = $vehicle ? (int)$vehicle[0]['type'] : null;
                    $id = $vehicle ? (int)$vehicle[0]['id'] : null;
                    $reference = $vehicle ? (string)$vehicle[0]['reference'] : null;
                    // Check if the customer is barred (only if verified)
                    if ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number)) {
                        $status = 'card';
                    }
                } elseif ($isKnown) {
                    $status = 'known';
                    // Check if the vehicle previously only has been set to a specific customer, and if so, get the customer name
                    $orders_o = new orders_o();
                    $order = $orders_o->getFieldsWhere([
                        'reg_1' => $reg,
                        'deleted_at' => null, // Only consider non-deleted orders
                    ], ['customer_id']);
                    $customer_ids = array_unique(array_map(function ($row) {
                        return (int)$row['customer_id'];
                    }, $order));
                    if (count($customer_ids) === 1) {
                        $customer_number = $customer_ids[0];
                        // Check if the customer is barred (only if known)
                        if ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number)) {
                            $status = 'unknown'; // If the customer is barred, we set the status to unknown (as we don't need to know about barred known vehicles)
                            $customer_number = null; // Clear the customer number as well
                        }
                    } else {
                        // Set the customer name to multiple known customers
                        $customer_name = 'Multiple known customers';
                    }
                } elseif ($isUnknown) {
                    $status = 'unknown';
                }
                // Get the customer name if we have a customer number
                if ($customer_number) {
                    $customer = (new users_o())->getUserByCustomerNumber((int)$customer_number);
                    if ($customer->exists()) {
                        $customer_name = (string)$customer->getCustomerName((int)$customer_number);
                    }
                }
                // Return the formatted vehicle
                return [
                    'reg' => $reg,
                    'status' => $status,
                    'type' => $type,
                    'customer_id' => $customer_number,
                    'id' => $id,
                    'last_order_id' => $last_order_id,
                    'reference' => $reference,
                    'booking_id' => $booking_id,
                    'booking_datetime' => $booking_datetime,
                    'notes' => $notes,
                    'customer_name' => $customer_name,
                    'barred' => $status === 'card', // If the status is 'card', the customer is barred
                ];
            }, $vehicles);
            // Return the results
            $response->success($vehicles);
        },
            [
                'search_vehicles' => 'Search vehicles by registration number',
            ]
        );
    }

    private static function getVerifiedRegs(string $search, array $options = ['limit' => null]): array
    {
        $customer_vehicles_o = new customer_vehicles_o();
        $results = $customer_vehicles_o->getFieldsWhereContaining(
            [
                'reg' => $search
            ],
            ['reg'],
            $options
        );
        return array_map(function ($row) {
            return (string)$row['reg'];
        }, $results);
    }

    private static function getKnownRegs(string $search, array $options = ['limit' => null]): array
    {
        $orders_o = new orders_o();
        $results = $orders_o->getFieldsWhereContaining(
            [
                'reg_1' => $search
            ],
            ['reg_1'],
            $options
        );
        return array_map(function ($row) {
            return (string)$row['reg_1'];
        }, $results);
    }

    private static function getBookedRegs(string $search, array $options = ['limit' => null], ?array $filters = []): array
    {
        $bookings_o = new order_bookings_o();
        /**
         * $bookings_o = new bookings_o();
         *
         * $results1 = $bookings_o->getFieldsWhereContaining(
         * [
         * 'regNrTraekker' => $search,
         * 'status' => 'pending',
         * ],
         * ['regNrTraekker'],
         * $options
         * );
         * $results2 = $bookings_o->getFieldsWhereContaining(
         * [
         * 'regNrTrailer' => $search,
         * 'status' => 'pending',
         * ],
         * ['regNrTrailer'],
         * $options
         * );
         */
        $results1 = $bookings_o->getFieldsWhereContaining(
            [
                'reg_1' => $search,
                'order_id' => null,
                'deleted_at' => null,
                ...$filters
            ],
            ['reg_1'],
            $options
        );
        $results2 = $bookings_o->getFieldsWhereContaining(
            [
                'reg_2' => $search,
                'order_id' => null,
                'deleted_at' => null,
                ...$filters
            ],
            ['reg_2'],
            $options
        );
        return array_unique(array_merge(
            array_map(function ($row) {
                return (string)$row['reg_1'];
            }, $results1),
            array_map(function ($row) {
                return (string)$row['reg_2'];
            }, $results2)
        ));
    }

    private static function getPendingOrderBookingSummaryByPlate(string $reg, array $filters = []): ?array
    {
        $bookings_o = new order_bookings_o();
        $fields = ['id', 'customer_number', 'reference', 'note', 'datetime'];
        $results1 = $bookings_o->getFieldsWhere([
            'reg_1' => $reg,
            ...$filters
        ], $fields) ?: [];
        $results2 = $bookings_o->getFieldsWhere([
            'reg_2' => $reg,
            ...$filters
        ], $fields) ?: [];

        $matches_by_key = [];
        foreach (array_merge($results1, $results2) as $booking) {
            if (!is_array($booking)) {
                continue;
            }

            $booking_id = isset($booking['id']) ? (int)$booking['id'] : 0;
            $dedupe_key = $booking_id > 0
                ? 'booking:' . $booking_id
                : implode('|', [
                    (string)($booking['reference'] ?? ''),
                    (string)($booking['note'] ?? ''),
                    (string)($booking['datetime'] ?? ''),
                ]);

            $matches_by_key[$dedupe_key] = $booking;
        }

        if (empty($matches_by_key)) {
            return null;
        }

        $matches = array_values($matches_by_key);
        usort($matches, function (array $left, array $right): int {
            $left_has_datetime = self::hasPendingOrderBookingSummaryDatetime($left);
            $right_has_datetime = self::hasPendingOrderBookingSummaryDatetime($right);
            if ($left_has_datetime !== $right_has_datetime) {
                return $right_has_datetime <=> $left_has_datetime;
            }

            $timestamp_difference = self::getPendingOrderBookingSummaryTimestamp($left) <=> self::getPendingOrderBookingSummaryTimestamp($right);
            if ($timestamp_difference !== 0) {
                return $timestamp_difference;
            }

            return ((int)($left['id'] ?? 0)) <=> ((int)($right['id'] ?? 0));
        });

        return $matches[0] ?? null;
    }

    private static function hasPendingOrderBookingSummaryDatetime(array $booking): bool
    {
        return trim((string)($booking['datetime'] ?? '')) !== '';
    }

    private static function getPendingOrderBookingSummaryTimestamp(array $booking): int
    {
        $raw_value = (string)($booking['datetime'] ?? '');
        if ($raw_value !== '') {
            $parsed_value = strtotime($raw_value);
            if ($parsed_value !== false) {
                return $parsed_value;
            }
        }

        $fallback_id = (int)($booking['id'] ?? PHP_INT_MAX);
        return $fallback_id > 0 ? $fallback_id : PHP_INT_MAX;
    }

    private static function getUnknownRegs(string $search, array $options = ['limit' => null]): array
    {
        // Unknown vehicles are vehicles that has been scanned by the LPR system, but are not in any of the other lists
        $plate_scans_o = new plate_scans_o();
        $results = $plate_scans_o->getFieldsWhereContaining(
            [
                'plate' => $search,
            ],
            ['plate'],
            $options
        );
        return array_map(function ($row) {
            return (string)$row['plate'];
        }, $results);
    }

    private function getData(mixed $data, $response)
    {
        if (!isset($data['type'])) {
            $response->error('Type is required', 400);
        }
        if (!isset($data['reg'])) {
            $response->error('Registration number is required', 400);
        }
        if (!isset($data['notes'])) {
            $response->error('Notes is required', 400);
        }
        return $data;
    }

    private function validateRegistrationNumber(mixed $reg, $response): void
    {
        if (!preg_match('/^[A-Z0-9]{4,10}$/', $reg)) {
            $response->error('Invalid registration number, it must be 4-10 characters long, and only contain uppercase letters and numbers', 400);
        }
    }

    private function validateType(mixed $type, $response): void
    {
        // Make sure the type is more than 2 characters
        if (strlen($type) < 2) {
            $response->error('Type is too short, it must be at least 2 characters', 400);
        }
        // Make sure the type is less than 50 characters
        if (strlen($type) > 50) {
            $response->error('Type is too long, it must be less than 50 characters', 400);
        }
    }

    private function validateNotes(mixed $notes, $response): void
    {
        // If the notes are set, make sure they are less than 250 characters
        if (isset($notes) && strlen($notes) > 250) {
            $response->error('Notes are too long, they must be less than 250 characters', 400);
        }
    }
}
