<?php

namespace routes;

use classes\authentication;
use classes\response;
use classes\router;
use classes\system_search_cache;
use classes\system_search_service;
use Throwable;
use traits\route_t;

class systemSearchRoute
{
    use route_t;

    public function run(): void
    {
        $this->get('/search/system', function () {
            $this->handleSearchRequest();
        });

        $this->post('/search/system', function () {
            $this->handleSearchRequest();
        });

        $this->delete('/superuser/search/system/cache', function () {
            global /** @var response $response */
            $response;
            $this->requirePermission('superuser_search_system_cache_clear');

            system_search_cache::clearAll();
            $response->success([
                'message' => 'System search cache cleared',
                'query_cache_cleared' => true,
            ]);
        }, [
            'superuser_search_system_cache_clear' => 'Clear system-wide search query caches',
        ]);

        $this->post('/superuser/search/system/cache/rebuild', function () {
            global /** @var response $response */
            $response;
            $this->requirePermission('superuser_search_system_cache_rebuild');

            $params = $this->getRequestPayload();
            $scope = strtolower(trim((string)($params['scope'] ?? 'all')));
            $types = $this->parseTypeList($params['types'] ?? []);
            $request = system_search_cache::enqueueRebuild($scope, $types);

            system_search_cache::clearQueryCaches();

            $response->success([
                'message' => 'System search cache rebuild queued',
                'request' => $request,
                'query_cache_cleared' => true,
            ]);
        }, [
            'superuser_search_system_cache_rebuild' => 'Queue and trigger a system-wide search cache rebuild',
        ]);
    }

    private function handleSearchRequest(): void
    {
        global /** @var response $response */
        /** @var router $router */
        $response, $router;

        $auth = new authentication();
        $user = $auth->get_user();
        $subuser = $auth->get_subuser();
        if ($user === false && $subuser === false) {
            $response->error('Invalid session', 401);
        }

        $params = $this->getRequestPayload();
        $query = trim((string)($params['q'] ?? $params['query'] ?? $params['search'] ?? ''));
        if ($query === '') {
            $response->error('Missing required parameter: query', 400);
        }

        $includeTypes = $this->parseTypeList($params['include_types'] ?? []);
        $excludeTypes = $this->parseTypeList($params['exclude_types'] ?? []);
        $includeAssociations = $this->toBool($params['include_associations'] ?? true, true);
        $maxResults = $this->clampMaxResults((int)($params['max_results'] ?? 50));

        [$allowedTypes, $ownOnlyTypes] = $this->resolveAllowedTypes();
        if (empty($allowedTypes)) {
            $response->forbidden($this->searchAccessPermissionCandidates());
        }

        $permissionsCatalogAll = $this->flattenPermissionCatalog((array)$router->getPermissions());
        $permissionsCatalogOwn = [];
        if ($user !== false) {
            try {
                $permissionsCatalogOwn = array_values(array_unique(array_map('strval', (array)$user->getGroup()->getPermissions())));
            } catch (Throwable) {
                $permissionsCatalogOwn = [];
            }
        }

        $service = new system_search_service();
        $result = $service->search([
            'query' => $query,
            'include_types' => $includeTypes,
            'exclude_types' => $excludeTypes,
            'allowed_types' => $allowedTypes,
            'own_only_types' => $ownOnlyTypes,
            'own_customer_number' => $this->resolveEffectiveCustomerNumber(),
            'allowed_department_ids' => $this->resolveAllowedDepartmentIds($user),
            'permissions_catalog_all' => $permissionsCatalogAll,
            'permissions_catalog_own' => $permissionsCatalogOwn,
            'module_config_visibility' => $this->buildModuleConfigVisibility(),
            'include_associations' => $includeAssociations,
            'max_results' => $maxResults,
        ]);

        $response->success($result);
    }

    private function getRequestPayload(): array
    {
        $payload = $this->getParametersAsArray();
        return is_array($payload) ? $payload : [];
    }

    private function resolveAllowedTypes(): array
    {
        $allowed = [];
        $ownOnly = [];
        foreach ($this->entityPermissionMap() as $type => $permissionSets) {
            $hasAll = $this->hasAnyPermission((array)($permissionSets['all'] ?? []));
            $hasOwn = $this->hasAnyPermission((array)($permissionSets['own'] ?? []));
            if (!$hasAll && !$hasOwn) {
                continue;
            }
            $allowed[] = $type;
            if (!$hasAll && $hasOwn) {
                $ownOnly[] = $type;
            }
        }
        return [
            array_values(array_intersect($this->allEntityTypes(), array_values(array_unique($allowed)))),
            array_values(array_intersect($this->allEntityTypes(), array_values(array_unique($ownOnly)))),
        ];
    }

    private function entityPermissionMap(): array
    {
        return [
            'objects' => [
                'all' => ['list_order_attachments', 'download_order_attachments', 'list_department_selfserve_task_attachments'],
                'own' => ['list_own_order_attachments', 'download_order_attachments_own'],
            ],
            'module_config' => [
                'all' => $this->moduleConfigPermissions(),
                'own' => [],
            ],
            'orders' => [
                'all' => ['list_orders', 'fetch_order'],
                'own' => ['list_own_orders', 'fetch_own_order'],
            ],
            'order_items' => [
                'all' => ['list_order_items'],
                'own' => ['list_own_order_items'],
            ],
            'customers' => [
                'all' => ['search_customers', 'list_users', 'get_user_from_customer_number'],
                'own' => ['user'],
            ],
            'employees' => [
                'all' => ['list_users'],
                'own' => [],
            ],
            'users' => [
                'all' => ['list_users', 'get_user'],
                'own' => ['user'],
            ],
            'subusers' => [
                'all' => ['list_subuser_grants', 'manage_subuser_grants'],
                'own' => ['list_own_subusers', 'list_own_subuser_grants'],
            ],
            'customer_discounts' => [
                'all' => ['get_custom_prices_other', 'set_custom_price'],
                'own' => [],
            ],
            'customer_fixed_prices' => [
                'all' => ['get_customer_fixed_pricing'],
                'own' => [],
            ],
            'departments' => [
                'all' => ['list_departments'],
                'own' => [],
            ],
            'permissions' => [
                'all' => ['permissions_list'],
                'own' => ['permissions_list_own'],
            ],
            'roles' => [
                'all' => ['list_roles'],
                'own' => [],
            ],
            'invoices' => [
                'all' => ['list_collected_invoices', 'list_collected_invoices_economic_overview'],
                'own' => ['user_invoices'],
            ],
            'vehicles' => [
                'all' => ['list_vehicles_other', 'list_unknown_customer_vehicles'],
                'own' => ['list_own_vehicles'],
            ],
            'bookings' => [
                'all' => ['list_bookings'],
                'own' => ['list_own_bookings'],
            ],
            'bookings_new' => [
                'all' => ['list_bookings', 'statistics_bookings_new'],
                'own' => ['list_own_bookings'],
            ],
            'branding' => [
                'all' => ['list_branding_options'],
                'own' => [],
            ],
            'categories' => [
                'all' => ['list_categories'],
                'own' => [],
            ],
            'currency_conversion_rates' => [
                'all' => ['modules_fxratesapi_rate', 'modules_fxratesapi_rates'],
                'own' => [],
            ],
            'customer_codes' => [
                'all' => ['get_customer_code', 'add_customer_code'],
                'own' => [],
            ],
            'customer_default_department' => [
                'all' => ['get_customer_default_department_other', 'add_customer_default_department_other', 'delete_customer_default_department_other'],
                'own' => ['get_customer_default_department', 'add_customer_default_department', 'delete_customer_default_department'],
            ],
            'customer_notes' => [
                'all' => ['list_customer_notes'],
                'own' => [],
            ],
            'customer_vehicles_addons' => [
                'all' => ['list_vehicles_addon_other', 'list_vehicle_customer_suggestions'],
                'own' => ['list_vehicle_addon_own'],
            ],
            'department_categories' => [
                'all' => ['list_department_categories'],
                'own' => [],
            ],
            'department_daily_reports' => [
                'all' => ['list_department_daily_reports'],
                'own' => [],
            ],
            'department_gates' => [
                'all' => ['list_department_gates'],
                'own' => [],
            ],
            'department_goals' => [
                'all' => ['goals_department_list'],
                'own' => [],
            ],
            'department_lanes' => [
                'all' => ['list_department_lanes'],
                'own' => [],
            ],
            'department_notification_sms' => [
                'all' => ['department_notification_sms_get'],
                'own' => [],
            ],
            'department_relays' => [
                'all' => ['list_department_relays'],
                'own' => [],
            ],
            'department_selfserve_condition_rules' => [
                'all' => ['list_department_selfserve_condition_rules'],
                'own' => [],
            ],
            'department_selfserve_conditions' => [
                'all' => ['list_department_selfserve_conditions'],
                'own' => [],
            ],
            'department_selfserve_questions' => [
                'all' => ['list_department_selfserve_questions'],
                'own' => [],
            ],
            'department_selfserve_tasks' => [
                'all' => ['list_department_selfserve_tasks'],
                'own' => [],
            ],
            'department_selfserve_vehicle_conditions' => [
                'all' => ['list_department_selfserve_vehicle_conditions'],
                'own' => ['list_own_department_selfserve_vehicle_conditions'],
            ],
            'department_time_bookings_entries' => [
                'all' => ['department_timebookings_entries_get'],
                'own' => [],
            ],
            'department_time_bookings_opening_hours' => [
                'all' => ['department_timebookings_opening_hours_get'],
                'own' => [],
            ],
            'department_time_bookings_types' => [
                'all' => ['department_timebookings_types_get'],
                'own' => [],
            ],
            'department_variables' => [
                'all' => ['superuser_fetch_department_variables', 'superuser_set_department_variables'],
                'own' => [],
            ],
            'fxratesapi_conversion_rates' => [
                'all' => ['modules_fxratesapi_rate', 'modules_fxratesapi_rates'],
                'own' => [],
            ],
            'module_action_logs' => [
                'all' => ['modules_action_logs_view'],
                'own' => [],
            ],
            'motorapi_lookups' => [
                'all' => ['modules_motorapi_lookup', 'department_license_plate_lookup'],
                'own' => [],
            ],
            'notifications' => [
                'all' => ['list_all_notifications', 'list_notifications'],
                'own' => ['list_own_notifications'],
            ],
            'order_bookings' => [
                'all' => ['list_bookings'],
                'own' => ['list_own_bookings'],
            ],
            'plate_scanners' => [
                'all' => ['list_number_plate_scanners', 'list_department_number_plate_scanners'],
                'own' => [],
            ],
            'plate_scans' => [
                'all' => ['list_number_plate_scans', 'list_number_plate_scans_department'],
                'own' => [],
            ],
            'product_options' => [
                'all' => ['list_product_options'],
                'own' => [],
            ],
            'products' => [
                'all' => ['list_products', 'economic_products_get'],
                'own' => [],
            ],
            'stripe_module_customers' => [
                'all' => ['modules_stripe_customers_list'],
                'own' => [],
            ],
            'stripe_module_orders' => [
                'all' => ['list_orders', 'fetch_order'],
                'own' => ['list_own_orders', 'fetch_own_order'],
            ],
            'stripe_payment_intents' => [
                'all' => ['get_payment_intent', 'confirm_payment_intent'],
                'own' => [],
            ],
            'subuser_grants' => [
                'all' => ['list_subuser_grants', 'manage_subuser_grants'],
                'own' => ['list_own_subuser_grants'],
            ],
            'xlvask_customers' => [
                'all' => ['modules_xlvask_customers'],
                'own' => [],
            ],
            'xlvask_potential_order_matches' => [
                'all' => ['list_potential_order_matches'],
                'own' => ['list_own_potential_order_matches'],
            ],
            'xlvask_usage_log_wash_items' => [
                'all' => ['modules_xlvask_usageLog', 'list_xlvask_usage_orders_all'],
                'own' => ['list_xlvask_usage_orders_own'],
            ],
            'xlvask_usage_logs' => [
                'all' => ['modules_xlvask_usageLog', 'list_xlvask_usage_orders_all'],
                'own' => ['list_xlvask_usage_orders_own'],
            ],
            'xlvask_vehicle_types' => [
                'all' => ['modules_xlvask_internal_vehicle_types'],
                'own' => [],
            ],
            'xlvask_vehicles' => [
                'all' => ['modules_xlvask_vehicles'],
                'own' => [],
            ],
        ];
    }

    private function moduleConfigPermissions(): array
    {
        return [
            'economic_config',
            'recaptcha_config',
            'email_config',
            'backups_config',
            'modules_bird_config',
            'motorapi_config',
            'stripe_config',
            'fxratesapi_config',
            'weatherapi_config',
            'gatewayapi_config',
            'xlvask_config',
            'entra_config',
            'modules_limble_config',
            'modules_ocrspace_config',
            'modules_openai_config',
            'modules_licenseplaterecognizer_config',
            'modules_virkdata_config',
            'modules_shelly_config',
            'modules_selfserve_config',
        ];
    }

    private function buildModuleConfigVisibility(): array
    {
        global $db;

        $modulePermissions = [
            'economic' => ['economic_config'],
            'reCAPTCHA' => ['recaptcha_config'],
            'Email' => ['email_config'],
            'Backups' => ['backups_config'],
            'bird' => ['modules_bird_config'],
            'motorapi' => ['motorapi_config'],
            'Stripe' => ['stripe_config'],
            'fxratesapi' => ['fxratesapi_config'],
            'weatherapi' => ['weatherapi_config'],
            'GatewayAPI' => ['gatewayapi_config'],
            'xlvask' => ['xlvask_config'],
            'Entra' => ['entra_config'],
            'limble' => ['modules_limble_config'],
            'ocrSpace' => ['modules_ocrspace_config'],
            'openAI' => ['modules_openai_config'],
            'licenseplaterecognizer' => ['modules_licenseplaterecognizer_config'],
            'virkdata' => ['modules_virkdata_config'],
            'shelly' => ['modules_shelly_config'],
            'selfserve' => ['modules_selfserve_config'],
        ];

        $visibility = [];
        try {
            $result = $db->query('SELECT DISTINCT module FROM module_config');
            if ($result instanceof \mysqli_result) {
                $rows = $db->fetch_all($result);
                foreach ($rows as $row) {
                    $module = (string)($row['module'] ?? '');
                    if ($module === '') {
                        continue;
                    }
                    $candidates = $modulePermissions[$module] ?? [];
                    if (empty($candidates)) {
                        $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '', $module) ?? '');
                        if ($slug !== '') {
                            $candidates[] = $slug . '_config';
                            $candidates[] = 'modules_' . $slug . '_config';
                        }
                    }
                    $visibility[$module] = $this->hasAnyPermission($candidates);
                }
            }
        } catch (Throwable) {
            // Fail open for compatibility if module map cannot be loaded.
        }

        return $visibility;
    }

    private function hasAnyPermission(array $permissions): bool
    {
        foreach ($permissions as $permission) {
            if (!is_string($permission) || $permission === '') {
                continue;
            }
            if ($this->hasPermission($permission)) {
                return true;
            }
        }
        return false;
    }

    private function flattenPermissionCatalog(array $permissions): array
    {
        $flat = [];
        $walker = function (mixed $node) use (&$flat, &$walker): void {
            if (!is_array($node)) {
                return;
            }
            foreach ($node as $key => $value) {
                if (is_string($key) && is_string($value)) {
                    $flat[$key] = $value;
                    continue;
                }
                if (is_array($value)) {
                    $walker($value);
                }
            }
        };
        $walker($permissions);
        return $flat;
    }

    /**
     * @param mixed $user
     * @return array<int, int>
     */
    private function resolveAllowedDepartmentIds(mixed $user): array
    {
        if ($user === false) {
            return [];
        }

        try {
            $departments = $user->getGroup()->getDepartments();
            if (!is_array($departments)) {
                return [];
            }
            return array_values(array_unique(array_map('intval', $departments)));
        } catch (Throwable) {
            return [];
        }
    }

    private function parseTypeList(mixed $value): array
    {
        $result = [];
        $raw = [];
        if (is_array($value)) {
            $raw = $value;
        } elseif (is_string($value)) {
            $trimmed = trim($value);
            if ($trimmed === '') {
                return [];
            }
            if (str_starts_with($trimmed, '[')) {
                $decoded = json_decode($trimmed, true);
                if (is_array($decoded)) {
                    $raw = $decoded;
                } else {
                    $raw = explode(',', $trimmed);
                }
            } else {
                $raw = explode(',', $trimmed);
            }
        } elseif ($value !== null) {
            $raw = [$value];
        }

        foreach ($raw as $item) {
            if (!is_string($item)) {
                continue;
            }
            $normalized = strtolower(trim($item));
            if ($normalized === '') {
                continue;
            }
            $result[] = $normalized;
        }
        return array_values(array_unique($result));
    }

    private function toBool(mixed $value, bool $default): bool
    {
        if (is_bool($value)) {
            return $value;
        }
        if (is_int($value) || is_float($value)) {
            return (bool)$value;
        }
        if (is_string($value)) {
            $parsed = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
            return $parsed ?? $default;
        }
        return $default;
    }

    private function clampInt(int $value, int $min, int $max, int $default): int
    {
        if ($value === 0) {
            $value = $default;
        }
        if ($value < $min) {
            return $min;
        }
        if ($value > $max) {
            return $max;
        }
        return $value;
    }

    private function clampMaxResults(int $value): int
    {
        return $this->clampInt($value, 1, 50, 50);
    }

    private function allEntityTypes(): array
    {
        return array_keys($this->entityPermissionMap());
    }

    /**
     * Return all permission keys that can unlock at least one searchable entity type.
     *
     * @return array<int, string>
     */
    private function searchAccessPermissionCandidates(): array
    {
        $permissions = [];
        foreach ($this->entityPermissionMap() as $permissionSets) {
            foreach ((array)($permissionSets['all'] ?? []) as $permission) {
                if (is_string($permission) && $permission !== '') {
                    $permissions[] = $permission;
                }
            }
            foreach ((array)($permissionSets['own'] ?? []) as $permission) {
                if (is_string($permission) && $permission !== '') {
                    $permissions[] = $permission;
                }
            }
        }
        return array_values(array_unique($permissions));
    }
}
