<?php

namespace routes;

use classes\authentication;
use classes\limited_backoffice_exception;
use classes\limited_backoffice_service;
use traits\route_t;

class limitedBackofficeRoute
{
    use route_t;

    public function run(): void
    {
        $this->get('/limited-backoffice/departments', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                return $service->departmentsForUser($user);
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
        ]);

        $this->get('/limited-backoffice/departments/{departmentId}/prices', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $departmentId = $this->routePositiveInt('departmentId');
                return $service->getDepartmentPrices($user, $departmentId);
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
        ]);

        $this->put('/limited-backoffice/departments/{departmentId}/prices', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_PRICES);
                $departmentId = $this->routePositiveInt('departmentId');
                return $service->updateDepartmentPrices($user, $departmentId, $this->requestPayload());
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_PRICES => 'Manage limited backoffice department prices',
        ]);

        $this->get('/limited-backoffice/departments/{departmentId}/customer-pricing', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING);
                $departmentId = $this->routePositiveInt('departmentId');
                $customerUserId = $this->queryCustomerUserId();
                return $service->getDepartmentCustomerPricing($user, $departmentId, $customerUserId);
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING => 'View limited backoffice department customer pricing',
        ]);

        $this->put('/limited-backoffice/departments/{departmentId}/customer-pricing', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING);
                $departmentId = $this->routePositiveInt('departmentId');
                $payload = $this->requestPayload();
                $customerUserId = $this->payloadCustomerUserId($payload);
                return $service->updateDepartmentCustomerPricing($user, $departmentId, $customerUserId, $payload);
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING => 'Manage limited backoffice department customer pricing',
        ]);

        $this->get('/limited-backoffice/roles', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
                return $service->rolePresets($user);
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES => 'Manage limited backoffice employees',
        ]);

        $this->get('/limited-backoffice/employees', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
                $includeInactive = strtolower((string)($this->fromQuery('include_inactive') ?? 'false')) === 'true';
                return $service->employeesForUser($user, $includeInactive);
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES => 'Manage limited backoffice employees',
        ]);

        $this->post('/limited-backoffice/employees', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
                return $service->createEmployee($user, $this->requestPayload());
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES => 'Manage limited backoffice employees',
        ]);

        $this->post('/limited-backoffice/employees/{employeeId}/migrate', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission('superuser');
                return $service->migrateEmployee($user, $this->routePositiveInt('employeeId'), $this->requestPayload());
            });
        }, [
            'superuser' => 'Migrate existing employees to limited backoffice employees',
        ]);

        $this->post('/limited-backoffice/employees/{employeeId}/login-link', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
                return $service->createEmployeeLoginLink($user, $this->routePositiveInt('employeeId'));
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES => 'Manage limited backoffice employees',
        ]);

        $this->put('/limited-backoffice/employees/{employeeId}', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
                return $service->updateEmployee($user, $this->routePositiveInt('employeeId'), $this->requestPayload());
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES => 'Manage limited backoffice employees',
        ]);

        $this->delete('/limited-backoffice/employees/{employeeId}', function () {
            $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array {
                $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS);
                $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
                return $service->deactivateEmployee($user, $this->routePositiveInt('employeeId'));
            });
        }, [
            limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice',
            limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES => 'Manage limited backoffice employees',
        ]);
    }

    private function withLimitedBackoffice(callable $callback): void
    {
        global $response;

        try {
            $user = (new authentication())->get_user();
            if (!$user) {
                $response->error('Invalid session', 400);
            }

            $response->success($callback(new limited_backoffice_service(), $user));
        } catch (limited_backoffice_exception $exception) {
            $response->error($exception->payload(), $exception->statusCode());
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function requestPayload(): array
    {
        $payload = json_decode(file_get_contents('php://input'), true);
        return is_array($payload) ? $payload : [];
    }

    private function routePositiveInt(string $name): int
    {
        $value = $this->fromRoute($name);
        if (!is_string($value) || !ctype_digit($value) || (int)$value <= 0) {
            throw new limited_backoffice_exception('Invalid route parameter.', 400);
        }
        return (int)$value;
    }

    private function queryPositiveInt(string $name): int
    {
        $value = $this->fromQuery($name);
        if (!is_string($value) || !ctype_digit($value) || (int)$value <= 0) {
            throw new limited_backoffice_exception('Invalid query parameter.', 400);
        }
        return (int)$value;
    }

    private function queryCustomerUserId(): int
    {
        if ($this->fromQuery('user_id') !== null) {
            return $this->queryPositiveInt('user_id');
        }

        return $this->userIdFromCustomerNumber($this->queryPositiveInt('customer_number'));
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function payloadPositiveInt(array $payload, string $name): int
    {
        $value = $payload[$name] ?? null;
        if (is_int($value) && $value > 0) {
            return $value;
        }
        if (!is_string($value) || !ctype_digit($value) || (int)$value <= 0) {
            throw new limited_backoffice_exception('Invalid request parameter.', 400);
        }
        return (int)$value;
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function payloadCustomerUserId(array $payload): int
    {
        if (array_key_exists('user_id', $payload)) {
            return $this->payloadPositiveInt($payload, 'user_id');
        }

        return $this->userIdFromCustomerNumber($this->payloadPositiveInt($payload, 'customer_number'));
    }

    private function userIdFromCustomerNumber(int $customerNumber): int
    {
        $customer = (new \objects\users_o())->getUserByCustomerNumber($customerNumber);
        if (!$customer->exists()) {
            throw new limited_backoffice_exception('Customer not found', 404);
        }

        return (int)$customer->id;
    }
}
