<?php

namespace routes;

use classes\authentication;
use objects\categories_o;
use objects\department_categories_o;
use objects\departments_o;
use objects\logs_o;
use objects\product_options_o;
use objects\products_o;
use objects\users_o;
use traits\route_t;

class productsRoute
{
    use route_t;

    /**
     * Get the customer object if the customer_id parameter is provided (In the request 'customer_id')
     * @return users_o|null
     */
    private function getCustomerIfProvided(): ?users_o
    {
        global $response;
        if (self::isParametersSet(['customer_id'])) {
            $customerId = (int)self::getParameter('customer_id');
            try {
                $customerObject = (new users_o())->getUserByCustomerNumber((int)$customerId);
                if ($customerObject->exists()) {
                    return $customerObject;
                }
            } catch (\Exception $e) {
                // Log the incident
                (new logs_o())->add('products', 'global', 3, 0, 'GET_CUSTOMER_FAILED', 'Failed to get customer with id ' . $customerId . '. Error: ' . $e->getMessage());
                // Return null
                return null;
            }
        }
        return null;
    }

    /**
     * Get the department id if the department_id parameter is provided (In the request 'department_id')
     * @return int|null
     */
    private function getDepartmentIdIfProvided(): ?int
    {
        global $response;
        if (self::isParametersSet(['department_id'])) {
            return (int)self::getParameter('department_id');
        }
        return null;
    }

    /**
     * Get the category (ID) if the category parameter is provided (In the request 'category')
     * @return int|null
     */
    private function getCategoryIfProvided(): ?int
    {
        global $response;
        if (self::isParametersSet(['category'])) {
            return (int)self::getParameter('category');
        }
        return null;
    }

    /**
     * Get the product id if the id parameter is provided (In the request 'id')
     * @return int|null
     */
    private function getProductIdIfProvided(): ?int
    {
        global $response;
        if (self::isParametersSet(['id'])) {
            return (int)self::getParameter('id');
        }
        return null;
    }

    /**
     * Parse the products price in the (optional) department pricing, with the (optional) customer discounts applied
     * @param array $products
     * @param users_o|null $customer
     * @param int|null $departmentId
     * @return array
     */
    private function parseProductsPrice(array $products, ?users_o $customer, ?int $departmentId): array
    {
        // Check if the departmentId is set
        if ($departmentId) {
            // Apply the departments unique pricing
            $products = (new products_o())->applyDepartmentPricing($products, $departmentId);
        }
        // Check if the customer is set
        if ($customer !== null) {
            // Apply the customers unique discounts
            $products = (new products_o())->applyCustomerDiscounts($products, $customer);
        }
        return $products;
    }

    /**
     * Parse the addons/options price in the (optional) department pricing, with the (optional) customer discounts applied
     * @param array $options
     * @param users_o|null $customer
     * @param int|null $departmentId
     * @return array
     */
    private function parseOptionsPrice(array $options, ?users_o $customer, ?int $departmentId): array
    {
        foreach ($options as $key => $option) {
            // Get the options product
            $product = $option['product'];
            // Apply the department pricing and customer discounts to the product
            $product = self::parseProductsPrice([$product], $customer, $departmentId)[0];
            // Update the option with the new product
            $options[$key]['product'] = $product;
            // Set the price of the option to the price of the product
            $options[$key]['price'] = (int)$product['price'];
        }
        return $options;
    }



    public function run(): void
    {
        $this->get('/products', function () {
            // Check if the user is logged in
            global $response;
            $permission_node = 'list_products';
            $isProductDetailsRestricted = true;
            if ($this->isAuthenticated()) {
                $isProductDetailsRestricted = false;
                $this->requirePermission($permission_node);
            }
            // Get the user object
            $user = (new authentication())->get_user();
            // Set the user id to 0 if guest
            $responsibleUserId = $isProductDetailsRestricted ? 0 : $user->id;
            function parseProduct($product, $isGuest): array
            {
                $tmpProduct = [
                    'id' => (int)$product['id'],
                    'name' => (string)$product['name'],
                    'description' => (string)$product['description'],
                    'price' => (int)$product['price'],
                    'subscription_allowed' => (boolean)$product['subscription_allowed'],
                    'category' => (int)$product['category'],
                    'piktogram' => (string)$product['piktogram'],
                    'economic_product_id' => (int)$product['economic_product_id'],
                    'apply_category_discount' => (boolean)$product['apply_category_discount'],
                    'requires_note' => (boolean)$product['requires_note'],
                    'created_at' => (string)$product['created_at'],
                    'updated_at' => (string)$product['updated_at'],
                    'addons' => (new product_options_o())->getProductOptions($product['id']),
                    'is_wash' => (bool)$product['is_wash'],
                    'display_in_booking_form' => (bool)$product['display_in_booking_form'],
                    'order_priority' => (int)$product['order_priority'],
                ];
                /** Apply guest filters and redactions, if any */
                if ($isGuest) {

                    $tmpProductGuest = [
                        ...$tmpProduct,
                        'name' => $tmpProduct['display_in_booking_form'] ? $tmpProduct['name'] : 'Login to view',
                        'description' => '',
                        'price' => 0,
                        'economic_product_id' => 0,
                        'apply_category_discount' => false,
                        'requires_note' => false,
                        'addons' => $tmpProduct['display_in_booking_form'] ?
                            array_map(function ($option) {
                            if ($option['product']['display_in_booking_form'] === false) {
                                $option['product']['name'] = 'Login to view';
                                $option['product']['description'] = '';
                                $option['product']['economic_product_id'] = 0;
                                $option['product']['apply_category_discount'] = false;
                                $option['product']['requires_note'] = false;
                                $option['product']['restricted'] = true;
                            }
                            $option['price'] = 0;
                            $option['product']['price'] = 0;
                            $option['restricted'] = $option['product']['restricted'] ?? false;
                            return $option;
                        }, $tmpProduct['addons']) : [],
                        'restricted' => !$tmpProduct['display_in_booking_form']
                    ];
                }
                return $isGuest ? $tmpProductGuest : $tmpProduct;
            }

            // Check if the request was successful
            if ($user || $isProductDetailsRestricted) {
                // Define the variables
                $customer = self::getCustomerIfProvided(); // This is only used if the customer_id parameter is provided
                $departmentId = self::getDepartmentIdIfProvided(); // This is only used if the department_id parameter is provided
                $category = self::getCategoryIfProvided(); // This is only used if the category parameter is provided (ID of the category)
                $productId = self::getProductIdIfProvided(); // This is only used if the id parameter is provided (ID of the product)
                // Check if the "final_price" parameter is set, and true.
                if (self::isParametersSet(['final_price']) && self::getParameter('final_price') === 'true') {
                    // Determine the products to return
                    if ($category) {
                        // Get products in the category
                        $products = (new products_o())->listObjectsByCategory($category);
                        // If the category is "6" exterior washes, include category "1" and "3"
                        if ($category === 6) {
                            $additionalProducts = (new products_o())->listObjectsByCategory(1);
                            $products = array_merge($products, $additionalProducts);
                            $additionalProducts = (new products_o())->listObjectsByCategory(3);
                            $products = array_merge($products, $additionalProducts);
                        }
                    } elseif ($productId) {
                        // Get the specific product
                        $product = (new products_o())->select($productId);
                        if ($product->exists()) {
                            $products = [$product->asArray()];
                        } else {
                            $products = [];
                        }
                    } else {
                        // Get all products
                        $products = (array)(new products_o())->listObjectsWithPaginationIfSet(
                            function ($product) use ($isProductDetailsRestricted) {
                                return parseProduct($product, $isProductDetailsRestricted);
                            }
                        );
                    }
                    // Return all products, with the department pricing and customer discounts applied
                    //$response->success(
                    //    array_map(function ($product) {
                    //        return parseProduct($product);
                    //    }, self::parseProductsPrice($products, $customer, $departmentId))
                    //);
                    $result = array_map(function ($product) use ($customer, $departmentId, $isProductDetailsRestricted) {
                        $productArray = parseProduct($product, $isProductDetailsRestricted);
                        // Get the price of the product with the department pricing and customer discounts applied
                        $productArray = parseProduct(self::parseProductsPrice([$productArray], $customer, $departmentId)[0], $isProductDetailsRestricted);
                        // Get the options for the product
                        $productArray['addons'] = self::parseOptionsPrice($productArray['addons'], $customer, $departmentId);
                        // Return the product with the updated price
                        return $productArray;
                    }, $products);
                    // If the productId is set, return a single object instead of an array
                    $response->success($productId && count($result) > 0 ? $result[0] : $result);
                }
                // Check if the id is set in the request (to get a specific product)
                if (self::isParametersSet(['id'])) {
                    // Log the incident
                    (new logs_o())->add('products', 'global', 1, $responsibleUserId, 'LIST_PRODUCTS', 'Successfully listed product with id ' . $response->getRequestParameter('id'));
                    // Return the product
                    $response->success(
                        parseProduct(
                            (new products_o())->select((int)self::getParameter('id'))->asArray(), $isProductDetailsRestricted
                        )
                    );
                }
                // Check if the category is set in the request
                $data = $_GET ?? [];
                // Check if the category is set
                if (isset($data['category'])) {
                    // Log the incident
                    (new logs_o())->add('products', 'global', 1, $responsibleUserId, 'LIST_PRODUCTS', 'Successfully listed products in category ' . $data['category']);
                    // Return the list of products
                    $products = (new products_o())->listObjectsByCategory($data['category']);
                    // Check if the department_id is set
                    if (isset($data['department_id'])) {
                        // Apply the departments unique pricing
                        $products = (new products_o())->applyDepartmentPricing((array)$products, (int)$data['department_id']);
                    }
                    $response->success(
                        array_map(function ($product) use ($isProductDetailsRestricted) {
                            return parseProduct($product, $isProductDetailsRestricted);
                        }, $products)
                    );
                }
                // Log the incident
                (new logs_o())->add('products', 'global', 1, $responsibleUserId, 'LIST_PRODUCTS', 'Successfully listed products');
                // Check if the department_id is set
                if (isset($data['department_id'])) {
                    // Get all product ids contained in a category attached to the department
                    $departmentSpecificProducts = (new departments_o())->select((int)$data['department_id'])->getAllProductInDepartmentCategories();
                    // Get the product ids as an array
                    $departmentSpecificProductIds = array_map(function ($product) {
                        return $product->id;
                    }, $departmentSpecificProducts);
                    // Return the list of products
                    $response->success(
                        (new products_o())->applyDepartmentPricing((array)(new products_o())->listObjectsWithPaginationIfSet(
                            function ($product) use ($isProductDetailsRestricted, $departmentSpecificProductIds) {
                                // Only include products that are in the department specific product ids
                                return parseProduct($product, $isProductDetailsRestricted);
                            },
                            (new products_o())->forceRestrictFilters([
                                'id' => $departmentSpecificProductIds,
                            ])
                        ), (int)$data['department_id'])
                    );
                }
                // Return the list of products
                $response->success(
                    (new products_o())->listObjectsWithPaginationIfSet(function ($product) use ($isProductDetailsRestricted) {
                        return parseProduct($product, $isProductDetailsRestricted);
                    })
                );
            } else {
                // Log the incident
                (new logs_o())->add('products', 'global', 1, 0, 'LIST_PRODUCTS', 'No user found, or invalid session');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'list_products' => 'List all products'
            ]
        );

        $this->post('/products', function () {
            // Require the user to be logged in
            global $response;
            $this->requirePermission('add_product');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Get the request data
                $name = $response->getRequestParameter('name');
                $description = $response->getRequestParameter('description') ?? null;
                $price = $response->getRequestParameter('price');
                $category = $response->getRequestParameter('category') ?? null;
                $piktogram = $response->getRequestParameter('piktogram') ?? null;
                $economicProductId = $response->getRequestParameter('economicProductId') ?? null;
                // Check if the required fields are set
                self::requireParameters(
                    [
                        'name',
                        'price'
                    ]
                );
                // Add the product
                (new products_o())->add(
                    (string)$name,
                    (string)$description ?? '',
                    (int)$price,
                    (int)$category ?? '',
                    (int)$piktogram ?? null,
                    (int)$economicProductId ?? null
                );
                // Log the incident
                (new logs_o())->add('products', 'global', 1, $user->id, 'ADD_PRODUCT', 'Product name: ' . $name);
                // Return a success message
                $response->success(['message' => 'Product added successfully']);
            } else {
                // Log the incident
                (new logs_o())->add('products', 'global', 1, 0, 'ADD_PRODUCT', 'No user found, or invalid session');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'add_product' => 'Add a product'
            ]
        );

        $this->put('/products', function () {
            // Require the user to be logged in
            global $response;
            $this->requirePermission('edit_product');
            // Get the user object
            $user = (new authentication())->get_user();
            // Check if the request was successful
            if ($user) {
                // Check if the required fields are set
                self::requireParameters(['id']);
                $id = $response->getRequestParameter('id');
                $product = (new products_o())->select((int)$id);
                // Check if the product exists
                if (!$product->exists()) {
                    $response->error('Product not found', 404);
                }
                // Update the fields that are set
                if (self::isParametersSet(['name'])) {
                    $product->name->set($response->getRequestParameter('name'));
                }
                if (self::isParametersSet(['description'])) {
                    $product->description->set($response->getRequestParameter('description') ?? '');
                }
                if (self::isParametersSet(['price'])) {
                    $product->price->set($response->getRequestParameter('price'));
                }
                if (self::isParametersSet(['subscription_allowed'])) {
                    $product->subscription_allowed->set($response->getRequestParameter('subscription_allowed') ? 1 : 0);
                }
                if (self::isParametersSet(['category'])) {
                    $product->category->set($response->getRequestParameter('category') ?? '');
                }
                if (self::isParametersSet(['piktogram'])) {
                    $product->piktogram->set($response->getRequestParameter('piktogram') ?? '');
                }
                if (self::isParametersSet(['economic_product_id'])) {
                    $product->economic_product_id->set($response->getRequestParameter('economic_product_id') ?? '');
                }
                if (self::isParametersSet(['apply_category_discount'])) {
                    $product->apply_category_discount->set($response->getRequestParameter('apply_category_discount') ? 1 : 0);
                }
                if (self::isParametersSet(['requires_note'])) {
                    $product->requires_note->set($response->getRequestParameter('requires_note') ? 1 : 0);
                }
                if (self::isParametersSet(['is_wash'])) {
                    $product->is_wash->set($response->getRequestParameter('is_wash') ? 1 : 0);
                }
                if (self::isParametersSet(['display_in_booking_form'])) {
                    $product->display_in_booking_form->set($response->getRequestParameter('display_in_booking_form') ? 1 : 0);
                }
                if (self::isParametersSet(['order_priority'])) {
                    $product->order_priority->set((int)$response->getRequestParameter('order_priority') ?? '');
                }
                (new logs_o())->add('products', 'global', 1, $user->id, 'EDIT_PRODUCT', 'Product id: ' . $id);
                // Return a success message
                $response->success(['message' => 'Product edited successfully']);
            } else {
                // Log the incident
                (new logs_o())->add('products', 'global', 1, 0, 'EDIT_PRODUCT', 'No user found, or invalid session');
                // Return an error
                $response->error('Invalid session', 400);
            }
        },
            [
                'edit_product' => 'Edit a product'
            ]
        );
    }
}