<?php

namespace helpers;

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

use classes\xlvask_automation_service;
use Exception;
use objects\orders_o;
use objects\users_o;
use objects\xlvask_customers_o;
use objects\xlvask_potential_order_matches_o;
use objects\xlvask_usage_logs_o;
use objects\xlvask_vehicles_o;

class xlvask_tasks
{
    public function __construct()
    {
        // Define the error messages if they are not already defined
        /**
         * if (!defined("ERROR_MESSAGE_ECONOMIC_INVOICE_NOT_FOUND")) {
         * define("ERROR_MESSAGE_ECONOMIC_INVOICE_NOT_FOUND", 'ECONOMIC_INVOICE_NOT_FOUND');
         * }
         * if (!defined("ERROR_MESSAGE_ECONOMIC_INVOICE_DRAFT_NOT_FOUND")) {
         * define("ERROR_MESSAGE_ECONOMIC_INVOICE_DRAFT_NOT_FOUND", 'ECONOMIC_INVOICE_DRAFT_NOT_FOUND');
         * }
         */
    }

    /**
     * @throws Exception
     */
    public function runCronTasks(): void
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Run the synchronization tasks
        if ($xlvask->config->synchronization_enabled->isTrue()) {
            // Synchronize users, this will keep the users (XL Vask customer objects) in cache up to date
            $this->runImportTasks();
            $this->runSyncUsers(true);
            $this->runSyncUsage();
            $this->runSyncVehicles();
            $this->runCleanupTasks();
        };
    }

    /**
     * Run the import tasks
     * This task is used to import the XL Vask vehicles, users, usage logs, and other necessary data into the system.
     * It will import the necessary classes and objects to be used in the system.
     * @return void
     * @throws Exception If the task fails
     */
    public function runImportTasks(): void
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Run the synchronization tasks
        if ($xlvask->config->synchronization_enabled->isTrue()) {
            // Synchronize users, this will keep the database tables updated:
            // - xlvask_customers
            // - xlvask_vehicles
            // - xlvask_usage_logs
            (new xlvask_customers_o())->importCustomers();
            (new xlvask_vehicles_o())->importVehicles();
            (new xlvask_usage_logs_o())->importUsageLogs();
            (new xlvask_automation_service())->runPending(null, null, [], 100, null);
        };
    }

    /**
     * Run the synchronize users task
     * This task fetches the users from XL Vask, and synchronizes them with this system.
     * This includes:
     * - Fetching the users from XL Vask
     * - Checking if the users exist in this system
     * - Add objects to the cache, when a matching user is found.
     * @return null|array['customers'] The customers fetched from XL Vask, null if synchronization is not enabled
     * @throws Exception If the task fails
     */
    public function runSyncUsers(bool $isSilent = false): array|null
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Check if synchronization is enabled
        if (!$xlvask->config->synchronization_enabled->isTrue()) {
            if (!$isSilent) {
                throw new Exception('XL Vask synchronization is not enabled.');
            }
            return null; // Synchronization is not enabled, do nothing
        }
        // Get the users from XL Vask
        $customer_objects = $this->fetchCustomers();
        //echo 'Customers fetched from XL Vask: ' . count($customer_objects) . PHP_EOL;
        // Return the customers with an external ID
        $customers = [
            'with_external_id' => [...self::filterCustomersWithExternalId($customer_objects)],
            'without_external_id' => [...self::filterCustomersWithoutExternalId($customer_objects)],
            'all' => $customer_objects,
        ];

        return self::synchronize_customers($customers['with_external_id']);
    }

    /**
     * Get the customers registered in XL Vask
     * This method fetches the customers from XL Vask and returns them as an array of customer objects.
     * @return array[xlvask_customer] An array of customer objects fetched from XL Vask
     * @throws Exception If the task fails
     */
    public function fetchCustomers(): array
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Get the users from XL Vask
        $customer_objects_raw = $xlvask->getCustomers();
        // Parse the users into customer objects
        return array_map(function ($customer) use ($xlvask) {
            /** @var xlvask_customer $tmp */
            $tmp = new $xlvask->helpers->xlvask_customer();
            foreach ( $customer as $key => $value ) {
                // Assign the properties shared by the xlvask_customer class.
                if (property_exists($tmp, $key)) {
                    $tmp->{$key} = $value;
                }
            }
            return $tmp; // Return the xlvask_customer object
        }, $customer_objects_raw);
    }

    /**
     * Get the customers with an external ID
     * @param array $customers Customers to filter
     * @return array The customers with an external ID
     */
    public static function filterCustomersWithExternalId(array $customers): array
    {
        return array_filter($customers, function ($customer) {
            /** @var xlvask_customer $customer */
            return !empty($customer->externId);
        });
    }

    /**
     * Get the customers without an external ID
     * @param array $customers Customers to filter
     * @return array The customers without an external ID
     */
    public static function filterCustomersWithoutExternalId(array $customers): array
    {
        return array_filter($customers, function ($customer) {
            /** @var xlvask_customer $customer */
            return empty($customer->externId);
        });
    }

    /**
     * Synchronize customers with external IDs
     * This method synchronizes the customers with external IDs from XL Vask.
     * It checks if the customers exist in this system, creates them if they do not exist,
     * and adds/removes the associated external ID to the users in this system.
     * @param array $with_external_id Customers with external IDs to synchronize
     * @return array|null The synchronized customers, null if synchronization is not enabled
     * @throws Exception
     */
    private static function synchronize_customers(array $with_external_id): array|null
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Check if synchronization is enabled
        if (!$xlvask->config->synchronization_enabled->isTrue()) {
            return null; // Synchronization is not enabled, do nothing
        }

        // Get a list of customer numbers that we need to synchronize
        $customer_numbers = array_map(function ($customer) {
            /** @var xlvask_customer $customer */
            return $customer->externId; // The customer number is stored in the externId property in XL Vask
        }, $with_external_id);

        // Get all the user ids associated with the customer numbers
        $users = (new users_o())->getUsersByCustomerNumbers($customer_numbers, true);

        $cache = $xlvask->getCache();

        /** @var xlvask_customer $customer */
        foreach ( $with_external_id as $customer ) {
            // Check if the customer exists in this system
            if (isset($users[$customer->externId])) {
                // The user exists, update the user with the external ID
                /** @var users_o $user */
                $user = $users[$customer->externId];
                // Cache the customer
                $cache->setCustomerCache((int)$customer->externId, $customer);
            }
        }
        return $with_external_id; // Return the synchronized customers
    }

    /**
     * Usage synchronization is intentionally disabled until the XL Vask integration is completed.
     */
    public function runSyncUsage(?string $dateFrom = null, ?string $dateTo = null): void
    {
        unset($dateFrom, $dateTo);
    }

    private static function formatUsageLogs(array $getUsageLog): array
    {
        //print_r($getUsageLog);
        // Format the usage logs to a more readable format
        // This is a placeholder, you might want to implement a method to format the logs
        return array_map(/**
         * @throws Exception
         */ function ($log) {
            $xlvask = new \classes\xlvask();
            return (new $xlvask->helpers->xlvask_usage_log())->setProperties($log);
        }, $getUsageLog);
    }

    private static function sortLogsByDate(array $logs): array
    {
        // Sort the logs by date
        usort($logs, function ($a, $b) {
            /** @var xlvask_usage_log $a */
            /** @var xlvask_usage_log $b */
            return strtotime($a->getFormattedDate()) - strtotime($b->getFormattedDate());
        });
        return $logs; // Return the sorted logs
    }

    /**
     * @throws Exception If the log is not completed, or if the customer does not have an externId, or if the customer does not exist in this system.
     */
    public function createOrderFromWash(xlvask_usage_log $log, xlvask_customer|\stdClass $customer): ?orders_o
    {
        // Make sure the XL Vask module is enabled
        $xlvask = new \classes\xlvask();
        $xlvask->requireModuleEnabled();

        // Check if the log is completed
        if (!$log->isCompleted()) {
            throw new Exception('The log ( ID: ' . $log->WashId . ' ) is not completed, cannot create an order from it.');
        }
        // Check if the customer has an externId
        if (empty($customer->externId)) {
            throw new Exception('The customer ( ID: ' . $customer->customerId . ' ) does not have an externId, cannot create an order from it.');
        }
        // Check if the customer exists in this system
        if (!$tmp_user = $customer->getUser()) {
            throw new Exception('The customer ( ID: ' . $customer->customerId . ' ) does not exist in this system, cannot create an order from it.');
        }
        // Create a new order object
        $order = new orders_o();
        $order->addXLVaskOrder(
            $tmp_user,
            $log,
        );
        // Return the order object
        return $order;
    }

    /**
     * Synchronize vehicles with XL Vask
     * This method is used to synchronize the vehicles with XL Vask.
     * It fetches the vehicles from XL Vask and adds them to the cache.
     * This includes:
     * - Fetching the vehicles from XL Vask
     * - Caching the vehicles in this system
     * @param bool $isSilent If true, will not throw an exception if synchronization is not enabled
     * @return null|array
     * @throws Exception If the task fails or if synchronization is not enabled
     * @see xlvask_vehicle
     * @see xlvask_cache::getVehicleCache()
     * @see xlvask_cache::setVehicleCache()
     * @see xlvask_cache::isVehicleCached()
     */
    public function runSyncVehicles(bool $isSilent = false): ?array
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Check if synchronization is enabled
        if (!$xlvask->config->synchronization_enabled->isTrue()) {
            if (!$isSilent) {
                throw new Exception('XL Vask synchronization is not enabled.');
            }
            return null; // Synchronization is not enabled, do nothing
        }
        // Get the vehicles from XL Vask
        /** @var xlvask_vehicles $vehicles */
        $vehicles = new $xlvask->helpers->xlvask_vehicles();
        $vehicles = $vehicles->getVehicles();
        // Cache the vehicles
        foreach ( $vehicles as $vehicle ) {
            /** @var xlvask_vehicle $vehicle */
            $xlvask->getCache()->setVehicleCache($vehicle->registrationNumber, $vehicle);
        }
        return $vehicles; // Return the vehicles
    }

    /**
     * Run the cleanup tasks
     * This task is used to clean up the XL Vask module.
     * @return void
     * @throws Exception If the task fails
     */
    public function runCleanupTasks(): void
    {
        // Define the XL Vask object
        $xlvask = new \classes\xlvask();
        // Require the module to be enabled
        $xlvask->requireModuleEnabled();
        // Run
    }
}
