<?php

namespace routes;

use classes\authentication;
use classes\economic;
use classes\gatewayapi;
use classes\response;
use classes\virkdata;
use Exception;
use modules\virkdata\helpers\virkdata_response;
use objects\logs_o;
use objects\subuser_grants_o;
use objects\subusers_o;
use objects\users_o;
use modules\subusers\permissions\subusers_permission_nodes_bookings;
use modules\subusers\permissions\subusers_permission_nodes_orders;
use modules\subusers\permissions\subusers_permission_nodes_selfserve;
use modules\subusers\permissions\subusers_permission_nodes_subusers;
use modules\subusers\permissions\subusers_permission_nodes_vehicles;
use modules\subusers\helpers\subusers_permission_node_key;
use traits\route_t;

class subusersRoute
{
    use route_t;

    private static function requireRegex(string $input, string $regex, string $error_message): void
    {
        if (!preg_match($regex, $input)) {
            global $response;
            $response->error($error_message, 400);
        }
    }

    public function run(): void
    {
        // =============================
        // Subuser Grant Management
        // =============================
        $this->get('/subusers/grants', function () {
            global $response;
            // Optional filters: customer_number, subuser_id
            $filters = ['deleted_at' => null];
            $hasFilter = false;
            if (self::isParametersSet(['customer_number'])) {
                $customer_number = (int)self::getParameter('customer_number');
                self::requireType($customer_number, self::type_int());
                $filters['billing_customer_number'] = $customer_number;
                $hasFilter = true;
            }
            if (self::isParametersSet(['subuser_id'])) {
                $subuser_id = (int)self::getParameter('subuser_id');
                self::requireType($subuser_id, self::type_int());
                $filters['subuser'] = $subuser_id;
                $hasFilter = true;
            }
            /// Check if the customer number can be taken from the current session's user (if it's not already provided as a filter)
            $user = (new authentication())->get_user();
            if (!$hasFilter && $user && !empty($user->customer_number->value())) {
                $filters['billing_customer_number'] = $user->customer_number->value();
                $hasFilter = true;
            }
            if (!$hasFilter) {
                $response->error('You must provide at least one of: customer_number or subuser_id', 400);
            }
            $paginatedResponse = (new subuser_grants_o())->listObjectsWithPaginationIfSet(function ($o) {
                $o = (object)$o;
                $subuser = (new subusers_o())->select((int)$o->subuser);
                return [
                    'id' => $o->id,
                    'billing_customer_number' => $o->billing_customer_number,
                    'subuser' => $o->subuser,
                    'name' => $subuser->name->value(),
                    'enabled' => $o->enabled,
                    'note' => $o->note,
                    'permissions' => json_decode($o->permissions, true) ?: [],
                    'created_at' => $o->created_at,
                    'updated_at' => $o->updated_at,
                ];
            }, $filters);
            $response->success($paginatedResponse);
        }, []);

        $this->post('/subusers/grants', function () {
            global $response;
            self::requireParameters(['customer_number', 'subuser_id']);
            $customer_number = (int)self::getParameter('customer_number');
            $subuser_id = (int)self::getParameter('subuser_id');
            self::requireType($customer_number, self::type_int());
            self::requireType($subuser_id, self::type_int());
            $enabled = true;
            if (self::isParametersSet(['enabled'])) {
                $enabled = (bool)filter_var(self::getParameter('enabled'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
                if ($enabled === null) { $enabled = true; }
            }
            $note = null;
            if (self::isParametersSet(['note'])) {
                $note = (string)self::getParameter('note');
                self::requireType($note, self::type_string());
                self::requireMaxLength('note', 65535);
            }
            $permissions = null;
            if (self::isParametersSet(['permissions'])) {
                $raw = self::getParameter('permissions');
                // Expect array (already parsed) or JSON string
                if (is_string($raw)) {
                    $decoded = json_decode($raw, true);
                    if (!is_array($decoded)) {
                        $response->error('Invalid permissions payload', 400);
                    }
                    $permissions = $decoded;
                } elseif (is_array($raw)) {
                    $permissions = $raw;
                } else {
                    $response->error('Invalid permissions type', 400);
                }
                // Validate each permission is a known key
                foreach ($permissions as $perm) {
                    if (!is_string($perm) || subusers_permission_node_key::tryFrom($perm) === null) {
                        $response->error('Unknown permission key: ' . (string)$perm, 400);
                    }
                }
            }
            try {
                $grant = (new subuser_grants_o())->add($customer_number, $subuser_id, (bool)$enabled, $note, $permissions);
                $response->success(['grant' => $grant->asArray()]);
            } catch (Exception $e) {
                $response->error('Failed to add subuser grant', 500);
            }
        }, []);

        $this->put('/subusers/grants', function () {
            global $response;
            self::requireParameters(['id']);
            self::requireType(self::getParameter('id'), self::type_int());
            $grant = (new subuser_grants_o())->select((int)self::getParameter('id'));
            if (!$grant->exists()) {
                $response->error('Grant not found', 404);
            }
            $user = (new authentication())->get_user();
            // Validate permissions
            if ((int)$grant->billing_customer_number->value() !== (int)$user->customer_number->value()) {
                self::requirePermission('manage_subuser_grants_other_customer');
            }
            // Update fields provided in the request
            if (self::isParametersSet(['enabled'])) {
                $enabled = (bool)filter_var(self::getParameter('enabled'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
                if ($enabled === null) { $enabled = false; }
                $grant->enabled->set($enabled);
            }
            if (self::isParametersSet(['note'])) {
                $note = (string)self::getParameter('note');
                self::requireType($note, self::type_string());
                self::requireMaxLength('note', 65535);
                $grant->note->set($note);
            }
            if (self::isParametersSet(['permissions'])) {
                $raw = self::getParameter('permissions');
                // Expect array (already parsed) or JSON string
                if (is_string($raw)) {
                    $decoded = json_decode($raw, true);
                    if (!is_array($decoded)) {
                        $response->error('Invalid permissions payload', 400);
                    }
                    $permissions = $decoded;
                } elseif (is_array($raw)) {
                    $permissions = $raw;
                } else {
                    $response->error('Invalid permissions type', 400);
                }
                // Validate each permission is a known key
                foreach ($permissions as $perm) {
                    if (!is_string($perm) || subusers_permission_node_key::tryFrom($perm) === null) {
                        $response->error('Unknown permission key: ' . (string)$perm, 400);
                    }
                }
                $grant->permissions->set($permissions);
            }
            $response->success($grant->asArray());
        },
            [
                "manage_subuser_grants_other_customer" => "Allow managing subuser grants for customers other than the one associated with the current session's user (use with caution, as this allows modifying permissions for subusers of other companies)"
            ]
        );

        $this->get('/subusers/permission-nodes', function () {
            global $response;
            // Build groups
            $groups = [
                new subusers_permission_nodes_bookings(),
                new subusers_permission_nodes_orders(),
                new subusers_permission_nodes_selfserve(),
                new subusers_permission_nodes_subusers(),
                new subusers_permission_nodes_vehicles(),
            ];
            $out = [];
            foreach ($groups as $group) {
                $nodes = [];
                foreach ($group->nodes as $key => $node) {
                    $nodes[] = [
                        'key' => $key,
                        'name' => $node->name,
                        'description' => $node->description,
                        'type' => $node->type->name,
                        'default' => (bool)($node->value ?? false),
                    ];
                }
                $out[] = [
                    'group' => $group->group_name,
                    'description' => $group->description,
                    'nodes' => $nodes,
                ];
            }
            $response->success($out);
        }, []);

        $this->post('/subusers', function () {
            global /** @var response $response */
            $response;
            self::requireParameters([
                'cvr',
                'phone_country_code',
                'phone'
            ]);

            $cvr = (int)self::getParameter('cvr');
            $phone_country_code =(int) self::getParameter('phone_country_code');
            $phone = (int)self::getParameter('phone');
            /** Validation */
            // Check if the CVR number is valid (8 digits)
            self::requireType($cvr, self::type_int());
            self::requireMinLength('cvr', 8);
            self::requireMaxLength('cvr', 8);
            // Check if the phone country code is valid (1-3 digits)
            self::requireType($phone_country_code, self::type_int());
            self::requireMinLength('phone_country_code', 1);
            self::requireMaxLength('phone_country_code', 3);
            // Check if the phone number is valid (4-15 digits)
            self::requireType($phone, self::type_int());
            self::requireMinLength('phone', 4);
            self::requireMaxLength('phone', 15);
            // Check if the CVR is registered to a company in the database
            $economic = new economic();
            $results = $economic->customers->customers->search([
                'corporateIdentificationNumber' => (string)$cvr,
            ],[
                'skipPages' => 0,
                'pageSize' => 1000, // Since the limit is 1000, we need to set the page size to 1000.
            ])->collection;
            if (count($results) === 0) {
                $response->error('Customer not found', 404);
            }
            /** At this point, we know that the CVR is valid and that the company exists in the database. */
            $company_user = (new users_o())->getUserByCustomerNumber((int)$results[0]->customerNumber);
            $company_user->requireSelected();
            /** We can now check if a subuser already exists with the same phone number. */
            $subuser = (new subusers_o())->getSubuserByPhone($phone_country_code, $phone);
            if ($subuser !== null) {
                $response->error('Account already exists with this phone number', 400);
            }
            /** We can now create the subuser and send a request to the company user to link the subuser to the company. */
            $subuser = (new subusers_o())->add(
                null,
                null,
                null,
                null,
                (int)$phone_country_code,
                (int)$phone
            );
            // Send an SMS with a link to complete the registration process.
            $gatewayAPI = new gatewayapi();
            if ($gatewayAPI->isEnabled()) {
                $token = $subuser->generateSetupToken();
                $link = 'https://truckwash.io/complete-registration?token=' . $token;
                $message = 'Tak for din oprettelse af chaufførkonto hos Truck Wash! Klik på linket for at fuldføre registreringen: ' . $link;
                $phone_number_array = [(string)$phone_country_code . (string)$phone];
                $gatewayAPI->send($phone_number_array, $message);
            }
            // Add the grant request
            $subuser_grants_o = new subuser_grants_o();
            try {
                $subuser_grants_o->add($results[0]->customerNumber, $subuser->id, false, null);
            } catch (Exception $e) {
                $response->error('Failed to add subuser grant', 500);
            }
            $response->success(['cvr' => $cvr, 'customer_number' => $results[0]->customerNumber]);
            // Code for creating a new subuser would go here
        });
        $this->get('/subusers/setup', function () {
            // Require the user to be logged in
            global $response;
            self::requireParameters(['token']);
            $token = self::getParameter('token');
            // Get the subuser with the setup token
            $subuser = (new subusers_o())->getSubuserBySetupToken($token);
            if ($subuser === null) {
                $response->error('Invalid or expired token', 400);
            }
            $response->success(['message' => 'Token is valid', 'subuser_id' => $subuser->id]);
        });
        $this->post('/subusers/setup', function () {
            // Require the user to be logged in
            global $response;
            self::requireParameters(['token', 'password', 'name']);
            $token = (string)self::getParameter('token');
            $password = (string)self::getParameter('password');
            $name = (string)self::getParameter('name');
            self::requireType($password, self::type_string());
            self::requireMinLength('password', 8);
            self::requireMaxLength('password', 255);
            self::requireRegex($password, '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/', 'Password must contain at least one uppercase letter, one lowercase letter, and one number');
            self::requireType($name, self::type_string());
            self::requireMinLength('name', 3);
            self::requireMaxLength('name', 255);
            /**
             * Optional fields:
             * - username: string, 3-255 characters
             * - email: string, valid email format, 3-255 characters
             */
            $username = null;
            $email = null;
            if (self::isParametersSet(['username'])) {
                $username = (string)self::getParameter('username');
                self::requireType($username, self::type_string());
                self::requireMinLength('username', 3);
                self::requireMaxLength('username', 255);
            }
            if (self::isParametersSet(['email'])) {
                $email = (string)self::getParameter('email');
                self::requireType($email, self::type_string());
                self::requireMinLength('email', 3);
                self::requireMaxLength('email', 255);
                if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                    $response->error('Invalid email format', 400);
                }
            }
            // Get the subuser with the setup token
            $subuser = (new subusers_o())->getSubuserBySetupToken($token);
            if ($subuser === null) {
                $response->error('Invalid or expired token', 400);
            }
            // Set the password for the subuser
            try {
                $subuser->setPassword($password);
                if (!empty($username) || !empty($email) || !empty($name)) {
                    $subuser->update([
                        ...(!empty($username) ? ['username' => $username] : []),
                        ...(!empty($email) ? ['email' => $email] : []),
                        ...(!empty($name) ? ['name' => $name] : []),
                    ]);
                }
                // Invalidate the setup token
                $subuser->invalidateSetupToken($token);
                $response->success(['message' => 'Complete registration successful']);
            } catch (Exception $e) {
                $response->error($e->getMessage(), 400);
            }
        });

        $this->post('/subusers/auth/password', function () {
            global $response;
            /**
             * Username types:
             * 1. Phone number & country code
             * 2. Subuser id
             * 3. Username
             */
            $phone_country_code = null;
            $phone = null;
            $subuser_id = null;
            $username = null;
            if (self::isParametersSet(['phone_country_code', 'phone'])) {
                $phone_country_code = (int)self::getParameter('phone_country_code');
                $phone = (int)self::getParameter('phone');
                self::requireType($phone_country_code, self::type_int());
                self::requireMinLength('phone_country_code', 1);
                self::requireMaxLength('phone_country_code', 3);
                self::requireType($phone, self::type_int());
                self::requireMinLength('phone', 4);
                self::requireMaxLength('phone', 15);
            } elseif (self::isParametersSet(['subuser_id'])) {
                $subuser_id = (int)self::getParameter('subuser_id');
                self::requireType($subuser_id, self::type_int());
            } elseif (self::isParametersSet(['username'])) {
                $username = (string)self::getParameter('username');
                self::requireType($username, self::type_string());
                self::requireMinLength('username', 3);
                self::requireMaxLength('username', 255);
            } else {
                $response->error('You must provide either phone_country_code & phone, subuser_id or username', 400);
            }
            // Get the subuser based on the provided username type
            $subuser = null;
            if ($phone_country_code !== null && $phone !== null) {
                $subuser = (new subusers_o())->getSubuserByPhone($phone_country_code, $phone);
            } elseif ($subuser_id !== null) {
                $subuser = (new subusers_o())->select($subuser_id);
            } elseif ($username !== null) {
                $subuser = (new subusers_o())->getSubuserByUsername($username);
            }
            if ($subuser === null) {
                $response->error('Subuser not found', 404);
            }
            self::requireParameters(['password']);
            $password = (string)self::getParameter('password');
            self::requireType($password, self::type_string());
            self::requireMinLength('password', 8);
            self::requireMaxLength('password', 255);
            try {
                if (password_verify($password, $subuser->password->value())) {
                    // Generate a new session for the subuser
                    $session = $subuser->generateSession();
                    $response->success(['session' => $session]);
                } else {
                    $response->error('Invalid password', 400);
                }
            } catch (Exception $e) {
                $response->error($e->getMessage(), 500);
            }
        });

        // =============================
        // Subusers - List & Get (with grant visibility)
        // =============================
        $this->get('/subusers', function () {
            global $response;
            // Require authenticated user
            $user = (new authentication())->get_user();
            if ($user === false) {
                $response->error('Unauthorized', 401);
            }
            $customerNumber = (int)$user->customer_number->value();
            if ($customerNumber === 0) {
                $response->error('Unauthorized', 401);
            }

            // Optional: include_non_enabled (boolean) — when true, include subusers that only have non-enabled grants
            $includeNonEnabled = false;
            if (self::isParametersSet(['include_non_enabled'])) {
                $tmp = filter_var(self::getParameter('include_non_enabled'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
                $includeNonEnabled = $tmp === null ? false : (bool)$tmp;
            }

            // Only list subusers that have an enabled grant for the caller's customer number
            $existsClause = sprintf(
                "EXISTS (SELECT 1 FROM `subuser_grants` sg WHERE sg.`subuser` = `subusers`.`id` AND %ssg.`deleted_at` IS NULL AND sg.`billing_customer_number` = %d)",
                $includeNonEnabled ? '' : 'sg.`enabled` = 1 AND ',
                $customerNumber
            );

            // Use pagination helper with additional where
            $objects = (new subusers_o())
                ->listObjectsWithPaginationIfSet(function ($o) use ($customerNumber) {
                    $o = (object)$o;
                    $permissions = (new subuser_grants_o())
                        ->getGrantsForSubuserAndCustomer((int)$o->id, $customerNumber);
                    return [
                        'id' => (int)$o->id,
                        'username' => $o->username,
                        'name' => $o->name,
                        'email' => $o->email,
                        'phone_country_code' => isset($o->phone_country_code) ? (int)$o->phone_country_code : null,
                        'phone' => isset($o->phone) ? (int)$o->phone : null,
                        'created_at' => $o->created_at ?? null,
                        'updated_at' => $o->updated_at ?? null,
                        'suspended_at' => $o->suspended_at ?? null,
                        'permissions' => $permissions,
                    ];
                }, null, [], $existsClause);

            $response->success($objects);
        }, []);

        $this->get('/subusers/me', function () {
            global $response;
            $subuser = (new authentication())->get_subuser();
            if ($subuser === false) {
                $response->error('Unauthorized', 401);
            }

            $grants = (new subuser_grants_o())->getFieldsWhere([
                'subuser' => $subuser->id,
                'enabled' => 1,
                'deleted_at' => null,
            ], ['permissions', 'billing_customer_number']);

            $result = [
                "id" => (int)$subuser->id,
                "username" => $subuser->username->value(),
                "name" => $subuser->name->value(),
                "email" => $subuser->email->value(),
                "phone_country_code" => $subuser->phone_country_code->value() !== null ? (int)$subuser->phone_country_code->value() : null,
                "phone" => $subuser->phone->value() !== null ? (int)$subuser->phone->value() : null,
                "grants" => array_map(function ($grant) {
                    return [
                        'billing_customer_number' => (int)$grant['billing_customer_number'],
                        'permissions' => json_decode($grant['permissions'], true) ?: [],
                    ];
                }, $grants),
                "created_at" => $subuser->created_at->value() ?? null,
                "updated_at" => $subuser->updated_at->value() ?? null,
                "suspended_at" => $subuser->suspended_at->value() ?? null,
            ];
            $response->success($result);
        }, []);
    }
}