<?php

namespace routes;

use classes\authentication;
use classes\response;
use classes\shelly_relay_inventory;
use dynamicimages\images\machine_1;
use modules\selfserve\config\selfserve_dynamic_image_size_c;
use modules\selfserve\selfserve_c;
use objects\categories_o;
use objects\department_lanes_o;
use objects\department_selfserve_tasks_o;
use objects\logs_o;
use traits\route_t;

class departmentLanesRoute
{
    use route_t;

    private const RELEVANT_DYNAMIC_IMAGE_MAX_WIDTH = 1600;

    public function run(): void
    {
        $this->get('/department/lanes/status-toggles', function () {
            global $response;

            $this->requirePermission('list_department_lanes');
            self::requireParameters(['department_id']);
            $department_id = (int)self::getParameter('department_id');
            self::requireType($department_id, self::type_int());
            self::requireMinValue($department_id, 1);
            self::requireDepartmentAccess($department_id);

            $user = (new authentication())->get_user();
            if (!$user) {
                (new logs_o())->add('department_lanes', 'global', 1, 0, 'LIST_DEPARTMENT_LANE_STATUS_TOGGLES', 'User tried to list department lane status toggles without being logged in');
                $response->error('Invalid session', 400);
            }

            (new logs_o())->add('department_lanes', 'global', 1, $user->id, 'LIST_DEPARTMENT_LANE_STATUS_TOGGLES', 'User listed department lane status toggles for department ' . $department_id);
            $response->success(
                array_map(
                    static fn (department_lanes_o $department_lane): array => $department_lane->asArray(),
                    (new department_lanes_o())->getDepartmentLanes($department_id)
                )
            );
        },
            [
                'list_department_lanes' => 'List department lane status toggles'
            ]
        );

        $this->get('/department/lanes', function () {

            // Require the user to be logged in
            global $response;
            $this->requirePermission('list_department_lanes');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Check if the id parameter is set
                if (self::isParametersSet(['id'])) {
                    // If it is, return the department lane with that id
                    $department_lane = (new department_lanes_o())->select((int)$response->getRequestParameter('id'));
                    // Check if the department lane exists
                    if (!$department_lane->exists()) {
                        // Return an error
                        $response->error('Department lane not found', 404);
                    }
                    self::requireDepartmentAccess((int)$department_lane->department->value());
                    // Log the incident
                    (new logs_o())->add('department_lanes', 'global', 1, $user->id, 'VIEW_DEPARTMENT_LANE', 'User viewed department lane with id ' . $department_lane->id);
                    // Return the department lane
                    $response->success(
                        $department_lane->asArray()
                    );
                }
                // Log the incident
                (new logs_o())->add('department_lanes', 'global', 1, $user->id, 'LIST_DEPARTMENT_LANES', 'User listed department lanes');
                // Return the list of departments
                $response->success(
                    (new department_lanes_o())
                        ->setSearchableFields([
                            // The fields that can be searched. This would otherwise make it possible to get secret information from the database, simply by searching for it and getting the result count back
                            'id',
                            'name',
                            'department',
                            'relay_in_id',
                            'relay_out_id',
                            'relay_machine_id',
                            'relay_machine_program_picker_id',
                            'relay_machine_cleaner_id',
                            'dynamic_image_id',
                            'machine_type_id',
                            'selfserve_enabled',
                        ])
                        ->listObjectsWithPaginationIfSet(
                            function ($department_lane) use ($user) {
                                // Create the department lane object
                                $department_lane_o = (new department_lanes_o())->select((int)$department_lane['id']);
                                // Return the object as an array
                                return $department_lane_o->asArray();
                            },
                            (new department_lanes_o())->forceRestrictFilters(
                                [
                                    // This makes sure that the user can only see lanes from departments they explicitly have access to
                                    'department' => $user->getGroup()->getDepartments(),
                                ]
                            )
                        )
                );
            } else {
                // Log the incident
                (new logs_o())->add('department_lanes', 'global', 1, 0, 'LIST_DEPARTMENT_LANES', 'User tried to list department lanes without being logged in');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'list_department_lanes' => 'List all department lanes'
            ]
        );

        $this->get('/department/lanes/relay-options', function () {

            global $response;
            $this->requirePermission('list_department_lanes');
            $user = (new authentication())->get_user();

            if ($user) {
                try {
                    $options = (new shelly_relay_inventory())->listRelayOptions();
                    (new logs_o())->add(
                        'department_lanes',
                        'global',
                        1,
                        $user->id,
                        'LIST_DEPARTMENT_LANE_RELAY_OPTIONS',
                        'User listed available Shelly relay options'
                    );
                    $response->success($options);
                } catch (\Throwable $e) {
                    (new logs_o())->add(
                        'department_lanes',
                        'global',
                        0,
                        $user->id,
                        'LIST_DEPARTMENT_LANE_RELAY_OPTIONS',
                        'Failed to list Shelly relay options: ' . $e->getMessage()
                    );
                    $response->error('Failed to fetch Shelly relay options: ' . $e->getMessage(), 400);
                }
            } else {
                (new logs_o())->add(
                    'department_lanes',
                    'global',
                    1,
                    0,
                    'LIST_DEPARTMENT_LANE_RELAY_OPTIONS',
                    'User tried to list Shelly relay options without being logged in'
                );
                $response->error('Invalid session', 400);
            }
        },
            [
                'list_department_lanes' => 'List available Shelly relay options for department lanes'
            ]
        );

        /**
         * Generate dynamic image for a department lane (machine UI)
         *
         * Query parameters:
         * - department (int, required)
         * - lane (int, required)
         * - buttons (array|json|csv, optional) → ordered highlighted button IDs (0-indexed), "reset", "start", or "program_picker"
         * - current_step (int >= 0, optional) → current click/step indicator
         * - only_current_step (bool/int, optional) → if true, only draw current step highlight
         * - vehicle_type (int|null, optional) → normalized but currently not used by machine_1
         *
         * Response: image/png
         */
        $this->get('/department/lanes/dynamic-image', function () {
            global $response;
            // Reuse listing permission; viewing image is tied to lane visibility
            // Authenticated user and department access validation
            //$this->requirePermission('view_department_lane_image');


            // Required params
            self::requireParameters(['department', 'lane']);
            $department_id = (int) self::getParameter('department');
            $lane_id = (int) self::getParameter('lane');
            self::requireType($department_id, self::type_int());
            self::requireType($lane_id, self::type_int());
            self::requireMinValue($department_id, 1);
            self::requireMinValue($lane_id, 1);


            // Load lane and verify it belongs to department
            $lane = (new department_lanes_o())->select($lane_id);
            if (!$lane->exists()) {
                $response->error('Department lane not found', 404);
            }
            if ((int)$lane->department->value() !== $department_id) {
                $response->error('Lane does not belong to the specified department', 400);
            }

            // Resolve dynamic image id → class (support id=1 for now)
            $dynamic_image_id = $lane->dynamic_image_id->value();
            if ($response->isRequestParameterSet('dynamic_image_id')) {
                $dynamic_image_override = $response->getRequestParameter('dynamic_image_id');
                if ($dynamic_image_override === null || $dynamic_image_override === '' || strtolower((string)$dynamic_image_override) === 'null') {
                    $dynamic_image_id = null;
                } else {
                    $dynamic_image_id = (int)$dynamic_image_override;
                    self::requireMinValue($dynamic_image_id, 1);
                }
            }
            if ($dynamic_image_id === null) {
                $response->error('No dynamic image configured for this lane', 404);
            }
            $dynamic_image_id = (int)$dynamic_image_id;
            $dynamic_image_size = self::getSelfServeDynamicImageSizeMode();

            // Parse optional params
            $buttons = null;
            if ($response->isRequestParameterSet('buttons')) {
                $buttons = $response->getRequestParameter('buttons');
                try {
                    $buttons = department_selfserve_tasks_o::normalizeButtonsInput($buttons);
                } catch (\Exception $e) {
                    $response->error('Invalid buttons parameter: ' . $e->getMessage(), 400);
                }
            }

            $current_step = 0;
            if ($response->isRequestParameterSet('current_step')) {
                $cs = (int)$response->getRequestParameter('current_step');
                self::requireMinValue($cs, 0);
                $current_step = $cs;
            }

            $only_current_step = false;
            if ($response->isRequestParameterSet('only_current_step')) {
                $val = $response->getRequestParameter('only_current_step');
                // Accept true/false, 1/0, '1'/'0'
                $only_current_step = in_array(strtolower((string)$val), ['1', 'true', 'yes', 'on'], true);
            }

            // Vehicle type (normalized; may be unused by concrete image class at present)
            $vehicle_type = null;
            if ($response->isRequestParameterSet('vehicle_type')) {
                try {
                    $vehicle_type = department_selfserve_tasks_o::normalizeVehicleTypeInput($response->getRequestParameter('vehicle_type'));
                } catch (\Exception $e) {
                    $response->error('Invalid vehicle_type parameter: ' . $e->getMessage(), 400);
                }
            }

            // Thumb position (normalized)
            $thumb_position = null;
            if ($response->isRequestParameterSet('thumb_position')) {
                $thumb_position = (int)$response->getRequestParameter('thumb_position');
                self::requireMinValue($thumb_position, 1);
                self::requireMaxValue($thumb_position, 12);
            }

            // Cache check
            $cacheKey = null;
            if (defined('redis')) {
                $cacheKey = self::buildDynamicImageCacheKey([
                    'dynamic_image_id' => $dynamic_image_id,
                    'buttons' => $buttons,
                    'current_step' => $current_step,
                    'only_current_step' => $only_current_step,
                    'vehicle_type' => $vehicle_type,
                    'dynamic_image_size' => $dynamic_image_size,
                    'thumb_position' => $thumb_position,
                ]);
                $cachedImage = $cacheKey === null ? false : redis->get($cacheKey);
                if ($cachedImage) {
                    header('Content-Type: image/png');
                    header('Content-Length: ' . strlen($cachedImage));
                    echo $cachedImage;
                    exit;
                }
            }

            // Instantiate and configure the image class based on dynamic_image_id
            switch ($dynamic_image_id) {
                case 1:
                    $image = new machine_1();
                    if ($thumb_position !== null) {
                        $image->thumb_position = $thumb_position;
                    }
                    break;
                default:
                    $response->error('Unsupported dynamic image id: ' . $dynamic_image_id, 400);
            }

            // Apply parameters
            if (is_array($buttons)) {
                $image->highlighted_buttons = $buttons;
            }
            $image->current_step = $current_step;
            $image->only_generate_current_step = (bool)$only_current_step;
            // Note: $vehicle_type currently not used by machine_1; reserved for future images.

            // Compose and serve the image
            try {
                $image->setup();
                if ($dynamic_image_size === selfserve_dynamic_image_size_c::SIZE_RELEVANT) {
                    $image->resizeToMaxWidth(self::RELEVANT_DYNAMIC_IMAGE_MAX_WIDTH);
                }

                if ($cacheKey && defined('redis')) {
                    $imageData = $image->exportBinary('png');
                    redis->setEx($cacheKey, $imageData, 86400); // Cache for 24 hours
                    header('Content-Type: image/png');
                    header('Content-Length: ' . strlen($imageData));
                    echo $imageData;
                    exit;
                }

                $image->servePicture('png');
                exit; // Ensure no extra output is appended
            } catch (\Exception $e) {
                $response->error('Failed to generate image: ' . $e->getMessage(), 500);
            }
        }, [
            'list_department_lanes' => 'View dynamic image for a department lane',
        ]);

        $this->post('/department/lanes', function () {

            // Require the user to be logged in
            global $response;
            $this->requirePermission('add_department_lane');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Log the incident
                (new logs_o())->add('department_lanes', 'global', 1, $user->id, 'ADD_DEPARTMENT_LANE', 'User added a department lane');
                // Get the request data
                $name = $response->getRequestParameter('name') ?? null;
                $department = $response->getRequestParameter('department') ?? null;
                $relay_in_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_in_id') ?? null);
                $relay_out_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_out_id') ?? null);
                $relay_machine_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_id') ?? null);
                $relay_machine_program_picker_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_program_picker_id') ?? null);
                $relay_machine_cleaner_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_cleaner_id') ?? null);
                
                $dynamic_image_id = $response->getRequestParameter('dynamic_image_id') ?? null;
                $machine_type_id = $response->getRequestParameter('machine_type_id') ?? null;
                $selfserve_enabled = self::isParametersSet(['selfserve_enabled'])
                    ? department_lanes_o::normalizeSelfServeEnabledValue($response->getRequestParameter('selfserve_enabled'))
                    : true;
                if ($dynamic_image_id !== null) {
                    $did = (int)$dynamic_image_id;
                    $this->requireType($did, $this->type_int());
                    $this->requireMinValue($did, 1);
                    $dynamic_image_id = $did;
                }
                if ($machine_type_id !== null && $machine_type_id !== '' && strtolower((string)$machine_type_id) !== 'null') {
                    $machine_type_id = (int)$machine_type_id;
                    $this->requireType($machine_type_id, $this->type_int());
                    $this->requireMinValue($machine_type_id, 1);
                } else {
                    $machine_type_id = null;
                }
                // Check if the required fields are set
                if ($name && $department) {
                    self::requireDepartmentAccess((int)$department);
                    // Add the department lane
                    $created_lane = (new department_lanes_o())->add((int)$department, (string)$name, $relay_in_id, $relay_out_id, $relay_machine_id, $relay_machine_program_picker_id, $relay_machine_cleaner_id, $dynamic_image_id, $machine_type_id, $selfserve_enabled);
                    // Return a success message
                    $response->success([
                        'message' => 'Department lane added',
                        'lane' => $created_lane->asArray(),
                    ]);
                } else {
                    // Return an error
                    $response->error('Missing required fields', 400);
                }

            } else {
                // Log the incident
                (new logs_o())->add('department_lanes', 'global', 1, 0, 'ADD_DEPARTMENT_LANE', 'User tried to add a department lane without being logged in');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'add_department_lane' => 'Add a department lane'
            ]
        );

        $this->put('/department/lanes', function () {

            // Require the user to be logged in
            global $response;
            $this->requirePermission('edit_department_lane');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Log the incident
                (new logs_o())->add('department_lanes', 'global', 1, $user->id, 'EDIT_DEPARTMENT_LANE', 'User edited a department lane');
                // Get the request data
                $id = $response->getRequestParameter('id') ?? null;
                $name = $response->getRequestParameter('name') ?? null;
                $department = $response->getRequestParameter('department') ?? null;
                $relay_in_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_in_id') ?? null);
                $relay_out_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_out_id') ?? null);
                $relay_machine_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_id') ?? null);
                $relay_machine_program_picker_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_program_picker_id') ?? null);
                $relay_machine_cleaner_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_cleaner_id') ?? null);
                $dynamic_image_id = $response->getRequestParameter('dynamic_image_id') ?? null;
                $machine_type_id = $response->getRequestParameter('machine_type_id') ?? null;

                // Check what fields are set
                self::requireParameters(['id']);
                // Get the department lane object
                $department_lane = (new department_lanes_o())->select((int)$id);
                // Check if the department lane exists
                if (!$department_lane->exists()) {
                    // Return an error
                    $response->error('Department lane not found', 404);
                }
                self::requireDepartmentAccess((int)$department_lane->department->value());
                $was_selfserve_enabled = $department_lane->isSelfServeEnabled();
                // Update the department lane fields that are set
                if (self::isParametersSet(['name'])) {
                    $department_lane->name->set($name);
                }
                if (self::isParametersSet(['department'])) {
                    self::requireDepartmentAccess((int)$department);
                    $department_lane->department->set((int)$department);
                }
                if (self::isParametersSet(['relay_in_id'])) {
                    self::syncDepartmentLaneRelayValue($department_lane->relay_in_id, $relay_in_id);
                }
                if (self::isParametersSet(['relay_out_id'])) {
                    self::syncDepartmentLaneRelayValue($department_lane->relay_out_id, $relay_out_id);
                }
                if (self::isParametersSet(['relay_machine_id'])) {
                    self::syncDepartmentLaneRelayValue($department_lane->relay_machine_id, $relay_machine_id);
                }
                if (self::isParametersSet(['relay_machine_program_picker_id'])) {
                    self::syncDepartmentLaneRelayValue($department_lane->relay_machine_program_picker_id, $relay_machine_program_picker_id);
                }
                if (self::isParametersSet(['relay_machine_cleaner_id'])) {
                    self::syncDepartmentLaneRelayValue($department_lane->relay_machine_cleaner_id, $relay_machine_cleaner_id);
                }
                if (self::isParametersSet(['dynamic_image_id'])) {
                    $param = $response->getRequestParameter('dynamic_image_id');
                    if ($param === null || $param === '' || (is_string($param) && strtolower($param) === 'null')) {
                        $department_lane->dynamic_image_id->nullify();
                    } else {
                        $did = (int)$param;
                        $this->requireType($did, $this->type_int());
                        $this->requireMinValue($did, 1);
                        $department_lane->dynamic_image_id->set($did);
                    }
                }
                if (self::isParametersSet(['machine_type_id'])) {
                    $param = $machine_type_id;
                    if ($param === null || $param === '' || (is_string($param) && strtolower($param) === 'null')) {
                        $department_lane->machine_type_id->nullify();
                    } else {
                        $machineTypeId = (int)$param;
                        $this->requireType($machineTypeId, $this->type_int());
                        $this->requireMinValue($machineTypeId, 1);
                        $department_lane->machine_type_id->set($machineTypeId);
                    }
                }
                if (self::isParametersSet(['selfserve_enabled'])) {
                    $next_selfserve_enabled = department_lanes_o::normalizeSelfServeEnabledValue($response->getRequestParameter('selfserve_enabled'));
                    $department_lane->selfserve_enabled->set($next_selfserve_enabled);
                    if ($was_selfserve_enabled && !$next_selfserve_enabled) {
                        department_lanes_o::disableSelfServeRelaysBestEffort((int)$department_lane->id);
                    }
                }
                // Return a success message
                $response->success([
                    'message' => 'Department lane updated',
                    'lane' => $department_lane->asArray(),
                ]);
            } else {
                // Log the incident
                (new logs_o())->add('department_lanes', 'global', 1, 0, 'EDIT_DEPARTMENT_LANE', 'User tried to edit a department lane without being logged in');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'edit_department_lane' => 'Edit a department lane'
            ]
        );
    }

    private static function normalizeRelayRequestParameter(mixed $value): ?string
    {
        $normalized = trim((string)($value ?? ''));
        if ($normalized === '' || strtolower($normalized) === 'null') {
            return null;
        }

        return $normalized;
    }

    private static function syncDepartmentLaneRelayValue(mixed $field, mixed $value): void
    {
        $normalized = self::normalizeRelayRequestParameter($value);
        if ($normalized === null) {
            $field->nullify();
            return;
        }

        $field->set($normalized);
    }

    private static function getSelfServeDynamicImageSizeMode(): string
    {
        try {
            $mode = (string)(new selfserve_c())->dynamic_image_size->getVariableValue();
        } catch (\Throwable) {
            return selfserve_dynamic_image_size_c::SIZE_ORIGINAL;
        }

        return in_array($mode, [selfserve_dynamic_image_size_c::SIZE_ORIGINAL, selfserve_dynamic_image_size_c::SIZE_RELEVANT], true)
            ? $mode
            : selfserve_dynamic_image_size_c::SIZE_ORIGINAL;
    }

    /**
     * @param array{
     *     dynamic_image_id:int,
     *     buttons:array<int|string>|null,
     *     current_step:int,
     *     only_current_step:bool,
     *     vehicle_type:int|null,
     *     dynamic_image_size:string,
     *     thumb_position:int|null
     * } $variant
     */
    private static function buildDynamicImageCacheKey(array $variant): ?string
    {
        $cacheParams = [
            'dynamic_image_id' => (int)$variant['dynamic_image_id'],
            'buttons' => $variant['buttons'],
            'current_step' => (int)$variant['current_step'],
            'only_current_step' => (bool)$variant['only_current_step'],
            'vehicle_type' => $variant['vehicle_type'],
            'dynamic_image_size' => (string)$variant['dynamic_image_size'],
        ];

        if ($variant['thumb_position'] !== null) {
            $cacheParams['thumb_position'] = (int)$variant['thumb_position'];
        }

        $json = json_encode($cacheParams);
        return $json === false ? null : 'dynamic_image:' . md5($json);
    }
}
