<?php

namespace routes;

use classes\authentication;
use classes\edge_gateway_install_service;
use classes\edge_gateway_manager;
use classes\edge_gateway_operation_exception;
use classes\edge_gateway_operation_service;
use classes\edge_gateway_registry_service;
use classes\edge_gateway_view_service;
use classes\response;
use Exception;
use modules\selfserve\classes\selfserve_machine_signal;
use traits\route_t;

class edgeGatewaysRoute
{
    use route_t;

    public function run(): void
    {
        $this->get('/edge-gateways', fn() => $this->handleListGateways(), [
            'modules_shelly_config' => 'Manage department edge gateways for local Shelly control',
        ]);
        $this->get('/edge-gateways/{id}', fn() => $this->handleGatewayDetail(), [
            'modules_shelly_config' => 'View department edge gateway detail',
        ]);
        $this->get('/edge-gateways/{id}/tasks', fn() => $this->handleGatewayTasksPage(), [
            'modules_shelly_config' => 'View edge gateway task timeline',
        ]);
        $this->get('/edge-gateways/{id}/logs', fn() => $this->handleGatewayLogsPage(), [
            'modules_shelly_config' => 'View edge gateway logs',
        ]);
        $this->get('/edge-gateways/{id}/statistics', fn() => $this->handleGatewayStatisticsPage(), [
            'modules_shelly_config' => 'View edge gateway statistics',
        ]);
        $this->post('/edge-gateways/{id}/stream-session', fn() => $this->handleGatewayStreamSessionCreate(), [
            'modules_shelly_config' => 'Create an edge gateway live stream session',
        ]);
        $this->post('/edge-gateways/{id}/shell-sessions', fn() => $this->handleGatewayShellSessionCreate(), [
            'modules_shelly_config' => 'Create an edge gateway shell session',
        ]);
        $this->put('/edge-gateways/{id}', fn() => $this->handleGatewayUpdate(), [
            'modules_shelly_config' => 'Update edge gateway metadata and primary assignment',
        ]);
        $this->get('/edge-gateways/{id}/operations', fn() => $this->handleGatewayOperationsList(), [
            'modules_shelly_config' => 'List edge gateway operations',
        ]);
        $this->post('/edge-gateways/{id}/operations', fn() => $this->handleGatewayOperationCreate(), [
            'modules_shelly_config' => 'Queue an edge gateway operation',
        ]);
        $this->post('/edge-gateways/{id}/operations/{operationId}/cancel', fn() => $this->handleGatewayOperationCancel(), [
            'modules_shelly_config' => 'Cancel an active edge gateway operation',
        ]);
        $this->get('/edge-gateways/{id}/operations/{operationId}/events', fn() => $this->handleGatewayOperationEvents(), [
            'modules_shelly_config' => 'List edge gateway operation events',
        ]);
        $this->post('/edge-gateways/{id}/rotate-credentials', fn() => $this->handleGatewayCredentialRotate(), [
            'modules_shelly_config' => 'Rotate edge gateway credentials',
        ]);
        $this->post('/edge-gateways/install-token', fn() => $this->handleInstallTokenCreate(), [
            'modules_shelly_config' => 'Create a one-time Raspberry Pi edge gateway installer token',
        ]);
        $this->get('/edge-gateways/install-token/{id}/status', fn() => $this->handleInstallTokenStatus(), [
            'modules_shelly_config' => 'View edge gateway installer session status',
        ]);
        $this->post('/edge-gateways/{id}/discovery', fn() => $this->handleGatewayDiscovery(), [
            'modules_shelly_config' => 'Queue Shelly discovery through the local edge gateway',
        ]);
        $this->put('/edge-gateways/{id}/bindings', fn() => $this->handleBindingsUpdate(), [
            'modules_shelly_config' => 'Approve or override relay bindings for an edge gateway',
        ]);
        $this->delete('/edge-gateways/{id}', fn() => $this->handleGatewayDelete(), [
            'modules_shelly_config' => 'Delete an edge gateway registration',
        ]);
        $this->post('/departments/{id}/gateway-cutover', fn() => $this->handleDepartmentCutover(), [
            'modules_shelly_config' => 'Cut a department over from Shelly cloud to local edge gateways',
        ]);

        $this->get('/edge-agent/install-token/verify', fn() => $this->handleInstallTokenVerify());
        $this->post('/edge-agent/install-token/status', fn() => $this->handleAgentInstallTokenStatus());
        $this->get('/edge-agent/install.sh', fn() => $this->renderInstallScript());
        $this->get('/edge-agent/artifacts/manifest.json', fn() => $this->renderArtifact('manifest.json'));
        $this->get('/edge-agent/artifacts/agent.php', fn() => $this->renderArtifact('agent.php'));
        $this->get('/edge-agent/artifacts/lan-worker.php', fn() => $this->renderArtifact('lan-worker.php'));
        $this->get('/edge-agent/artifacts/auto-updater.php', fn() => $this->renderArtifact('auto-updater.php'));
        $this->get('/edge-agent/artifacts/docker-compose.gateway.yml', fn() => $this->renderArtifact('docker-compose.gateway.yml'));
        $this->get('/edge-agent/artifacts/Dockerfile.edge-agent', fn() => $this->renderArtifact('Dockerfile.edge-agent'));
        $this->get('/edge-agent/artifacts/Dockerfile.lan-worker', fn() => $this->renderArtifact('Dockerfile.lan-worker'));
        $this->get('/edge-agent/artifacts/Dockerfile.auto-updater', fn() => $this->renderArtifact('Dockerfile.auto-updater'));
        $this->get('/edge-agent/artifacts/gateway-launcher.sh', fn() => $this->renderArtifact('gateway-launcher.sh'));
        $this->get('/edge-agent/artifacts/truckwash-edge-gateway-stack.service', fn() => $this->renderArtifact('truckwash-edge-gateway-stack.service'));
        $this->get('/edge-agent/artifacts/truckwash-edge-agent.service', fn() => $this->renderArtifact('truckwash-edge-agent.service'));
        $this->post('/edge-agent/claim', fn() => $this->handleAgentClaim());
        $this->post('/edge-agent/gateways/{id}/heartbeat', fn() => $this->handleAgentHeartbeat());
        $this->post('/edge-agent/gateways/{id}/operations/next', fn() => $this->handleAgentOperationNext());
        $this->post('/edge-agent/gateways/{id}/operations/{operationId}/events', fn() => $this->handleAgentOperationEvent());
        $this->post('/edge-agent/gateways/{id}/operations/{operationId}/complete', fn() => $this->handleAgentOperationComplete());
        $this->post('/edge-agent/gateways/{id}/commands/poll', fn() => $this->handleAgentCommandPoll());
        $this->post('/edge-agent/gateways/{id}/commands/{jobId}/result', fn() => $this->handleAgentCommandResult());
        $this->post('/edge-agent/gateways/{id}/expected-relay-states', fn() => $this->handleAgentExpectedRelayStates());
        $this->post('/edge-agent/gateways/{id}/relay-state-results', fn() => $this->handleAgentRelayStateResults());
        $this->post('/edge-agent/gateways/{id}/presence', fn() => $this->handleAgentPresence());
        $this->post('/edge-agent/gateways/{id}/selfserve/machine-signal-bindings', fn() => $this->handleAgentSelfserveMachineSignalBindings());
        $this->post('/edge-agent/gateways/{id}/selfserve/machine-signal', fn() => $this->handleAgentSelfserveMachineSignal());

        $this->post('/edge-agent/internal/gateways/{id}/validate', fn() => $this->handleBrokerGatewayValidate());
        $this->post('/edge-agent/internal/gateways/{id}/presence', fn() => $this->handleBrokerGatewayPresence());
        $this->post('/edge-agent/internal/gateways/{id}/backlog', fn() => $this->handleBrokerGatewayBacklog());
        $this->post('/edge-agent/internal/gateways/{id}/telemetry', fn() => $this->handleBrokerGatewayTelemetry());
        $this->post('/edge-agent/internal/gateways/{id}/operations/{operationId}/events', fn() => $this->handleBrokerOperationEvent());
        $this->post('/edge-agent/internal/gateways/{id}/operations/{operationId}/complete', fn() => $this->handleBrokerOperationComplete());
        $this->post('/edge-agent/internal/gateways/{id}/logs', fn() => $this->handleBrokerGatewayLogEntry());
        $this->post('/edge-agent/internal/gateways/{id}/selfserve/machine-signal', fn() => $this->handleBrokerSelfserveMachineSignal());
        $this->post('/edge-agent/internal/browser-streams/validate', fn() => $this->handleBrokerBrowserStreamValidate());
        $this->post('/edge-agent/internal/shell-sessions/validate', fn() => $this->handleBrokerShellSessionValidate());
        $this->post('/edge-agent/internal/shell-sessions/opened', fn() => $this->handleBrokerShellSessionOpened());
        $this->post('/edge-agent/internal/shell-sessions/close', fn() => $this->handleBrokerShellSessionClose());
    }

    private function handleListGateways(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');

        $departmentId = self::isParametersSet(['department_id']) ? (int)self::getParameter('department_id') : null;
        $view = trim((string)$this->fromQuery('view'));
        if ($departmentId !== null && $departmentId > 0) {
            $this->requireDepartmentAccess($departmentId);
        }

        $payload = $this->views()->listGatewaysWithFleetUsage($departmentId, $view !== 'summary');
        $response->add_meta('fleet_usage', $payload['fleet_usage']);
        $response->success($payload['gateways']);
    }

    private function handleGatewayDetail(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $response->success($this->requireGatewayAccess((int)$this->fromRoute('id')));
    }

    private function handleGatewayTasksPage(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->manager()->buildGatewayTasksPage($gatewayId));
    }

    private function handleGatewayLogsPage(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->manager()->buildGatewayLogsPage($gatewayId));
    }

    private function handleGatewayStatisticsPage(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->manager()->buildGatewayStatisticsPage($gatewayId));
    }

    private function handleGatewayStreamSessionCreate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $payload = self::getParametersAsArray();
        $scopes = isset($payload['scopes']) && is_array($payload['scopes']) ? (array)$payload['scopes'] : [];
        $response->success($this->manager()->createBrowserStreamSession($gatewayId, $this->actorUserId(), $scopes), 201);
    }

    private function handleGatewayShellSessionCreate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $payload = self::getParametersAsArray();
        $reason = isset($payload['reason']) ? (string)$payload['reason'] : '';
        $cwd = isset($payload['cwd']) ? (string)$payload['cwd'] : null;
        $cols = isset($payload['cols']) ? (int)$payload['cols'] : null;
        $rows = isset($payload['rows']) ? (int)$payload['rows'] : null;
        try {
            $response->success($this->manager()->createShellSession($gatewayId, $this->actorUserId(), $reason, $cols, $rows, $cwd), 201);
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
                'diagnostics' => $exception->details,
            ], $exception->status);
        }
    }

    private function handleGatewayUpdate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        self::requireParameters(['label', 'is_primary']);
        self::requireType(self::getParameter('label'), self::TYPE_STRING());
        self::requireType(self::getParameter('is_primary'), self::TYPE_BOOL());

        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);

        $response->success($this->registry()->updateGatewayMetadata($gatewayId, [
            'label' => (string)self::getParameter('label'),
            'is_primary' => (bool)self::getParameter('is_primary'),
        ], $this->actorUserId()));
    }

    private function handleGatewayOperationsList(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->operations()->listOperations($gatewayId));
    }

    private function handleGatewayOperationCreate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        self::requireParameters(['type', 'request']);
        self::requireType(self::getParameter('type'), self::TYPE_STRING());
        self::requireType(self::getParameter('request'), self::TYPE_ARRAY());

        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);

        try {
            $operation = $this->operations()->queueOperation(
                $gatewayId,
                (string)self::getParameter('type'),
                (array)self::getParameter('request'),
                $this->actorUserId()
            );
            $response->success([
                'operation' => $operation,
                'gateway' => $this->views()->getGateway($gatewayId),
            ], 201);
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleGatewayOperationEvents(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $operationId = (int)$this->fromRoute('operationId');
        self::requireParameterIntPositive($operationId, 'operationId');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->operations()->listOperationEvents($gatewayId, $operationId));
    }

    private function handleGatewayOperationCancel(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $operationId = (int)$this->fromRoute('operationId');
        self::requireParameterIntPositive($operationId, 'operationId');
        $this->requireGatewayAccess($gatewayId);

        try {
            $operation = $this->operations()->cancelOperation($gatewayId, $operationId, $this->actorUserId());
            $response->success([
                'operation' => $operation,
                'gateway' => $this->views()->getGateway($gatewayId),
            ]);
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleGatewayCredentialRotate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->operations()->rotateCredentials($gatewayId, $this->actorUserId()));
    }

    private function handleInstallTokenCreate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        self::requireParameters(['department_id']);

        $departmentId = (int)self::getParameter('department_id');
        self::requireParameterIntPositive($departmentId, 'department_id');
        $this->requireDepartmentAccess($departmentId);

        $response->success(
            $this->registry()->createInstallToken(
                $departmentId,
                self::isParametersSet(['label']) ? (string)self::getParameter('label') : null,
                $this->actorUserId()
            ),
            201
        );
    }

    private function handleInstallTokenStatus(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');

        $claimTokenId = (int)$this->fromRoute('id');
        self::requireParameterIntPositive($claimTokenId, 'id');

        $status = $this->registry()->getInstallTokenStatus($claimTokenId);
        $this->requireDepartmentAccess((int)$status['department_id']);
        unset($status['department_id']);
        $response->success($status);
    }

    private function handleGatewayDiscovery(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');

        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);

        $this->operations()->queueDiscoveryOperation($gatewayId, $this->actorUserId());
        $response->success($this->views()->getGateway($gatewayId));
    }

    private function handleBindingsUpdate(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        self::requireParameters(['bindings']);
        self::requireType(self::getParameter('bindings'), self::TYPE_ARRAY());

        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $this->registry()->setRelayBindings($gatewayId, (array)self::getParameter('bindings'), $this->actorUserId());
        $response->success($this->views()->getGateway($gatewayId));
    }

    private function handleGatewayDelete(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        $gatewayId = (int)$this->fromRoute('id');
        $this->requireGatewayAccess($gatewayId);
        $response->success($this->registry()->deleteGateway($gatewayId, $this->actorUserId()));
    }

    private function handleDepartmentCutover(): void
    {
        global /** @var response $response */ $response;
        $this->requirePermission('modules_shelly_config');
        self::requireParameters(['transport_mode']);
        $departmentId = (int)$this->fromRoute('id');
        self::requireParameterIntPositive($departmentId, 'id');
        $this->requireDepartmentAccess($departmentId);
        $response->success($this->registry()->setDepartmentTransportMode($departmentId, (string)self::getParameter('transport_mode'), $this->actorUserId()));
    }

    private function renderInstallScript(): void
    {
        $token = trim((string)$this->fromQuery('token'));
        if ($token === '') {
            http_response_code(400);
            echo 'Missing token';
            exit;
        }

        header('Content-Type: text/x-shellscript; charset=utf-8');
        echo $this->install()->buildInstallScript($token);
        exit;
    }

    private function handleInstallTokenVerify(): void
    {
        global /** @var response $response */ $response;
        $token = trim((string)$this->fromQuery('token'));
        if ($token === '') {
            $response->error('Missing token', 400);
        }

        try {
            $response->success($this->install()->verifyInstallToken($token));
        } catch (Exception $exception) {
            $response->error($exception->getMessage(), 400);
        }
    }

    private function handleAgentInstallTokenStatus(): void
    {
        global /** @var response $response */ $response;
        self::requireParameters(['token', 'status']);

        $payload = self::getParametersAsArray();
        try {
            $response->success($this->registry()->reportInstallTokenStatus(
                (string)$payload['token'],
                [
                    'status' => (string)$payload['status'],
                    'step' => isset($payload['step']) ? (string)$payload['step'] : null,
                    'message' => isset($payload['message']) ? (string)$payload['message'] : null,
                    'diagnostics' => isset($payload['diagnostics']) && is_array($payload['diagnostics']) ? (array)$payload['diagnostics'] : [],
                    'gateway_id' => isset($payload['gateway_id']) ? (int)$payload['gateway_id'] : null,
                    'last_error' => isset($payload['last_error']) ? (string)$payload['last_error'] : null,
                ]
            ));
        } catch (Exception $exception) {
            $response->error($exception->getMessage(), 400);
        }
    }

    private function renderArtifact(string $fileName): void
    {
        try {
            header('Content-Type: ' . $this->install()->contentType($fileName));
            echo $this->install()->readArtifact($fileName);
            exit;
        } catch (Exception $exception) {
            http_response_code(404);
            echo $exception->getMessage();
            exit;
        }
    }

    private function handleAgentClaim(): void
    {
        global /** @var response $response */ $response;
        self::requireParameters(['token']);
        $payload = self::getParametersAsArray();

        try {
            $response->success(
                $this->registry()->claimGateway(
                    (string)$payload['token'],
                    trim((string)($payload['hostname'] ?? gethostname() ?: 'unknown-gateway')),
                    isset($payload['installed_version']) ? (string)$payload['installed_version'] : null,
                    isset($payload['metadata']) && is_array($payload['metadata']) ? (array)$payload['metadata'] : []
                ),
                201
            );
        } catch (Exception $exception) {
            $response->error($exception->getMessage(), 400);
        }
    }

    private function handleAgentHeartbeat(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->registry()->recordHeartbeat($gatewayId, $this->requireAgentToken($payload), $payload));
    }

    private function handleAgentOperationNext(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();

        try {
            $response->success($this->operations()->claimNextOperation(
                $gatewayId,
                $this->requireAgentToken($payload),
                isset($payload['wait_seconds']) ? (int)$payload['wait_seconds'] : edge_gateway_manager::COMMAND_POLL_TIMEOUT_SECONDS,
                isset($payload['agent_instance_id']) ? (string)$payload['agent_instance_id'] : null
            ));
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleAgentOperationEvent(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $operationId = (int)$this->fromRoute('operationId');
        self::requireParameterIntPositive($operationId, 'operationId');
        $payload = self::getParametersAsArray();

        try {
            $response->success($this->operations()->appendAgentOperationEvent(
                $gatewayId,
                $operationId,
                $this->requireAgentToken($payload),
                $payload
            ));
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleAgentOperationComplete(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $operationId = (int)$this->fromRoute('operationId');
        self::requireParameterIntPositive($operationId, 'operationId');
        $payload = self::getParametersAsArray();

        try {
            $response->success($this->operations()->completeAgentOperation(
                $gatewayId,
                $operationId,
                $this->requireAgentToken($payload),
                $payload
            ));
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleAgentCommandPoll(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->pollCommand(
            $gatewayId,
            $this->requireAgentToken($payload),
            isset($payload['wait_seconds']) ? (int)$payload['wait_seconds'] : edge_gateway_manager::COMMAND_POLL_TIMEOUT_SECONDS
        ));
    }

    private function handleAgentCommandResult(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $jobId = (int)$this->fromRoute('jobId');
        self::requireParameterIntPositive($jobId, 'jobId');

        $payload = self::getParametersAsArray();
        $response->success($this->manager()->submitCommandResult(
            $gatewayId,
            $jobId,
            $this->requireAgentToken($payload),
            (bool)($payload['ok'] ?? false),
            isset($payload['result']) && is_array($payload['result']) ? (array)$payload['result'] : [],
            isset($payload['error']) ? (string)$payload['error'] : null
        ));
    }

    private function handleAgentExpectedRelayStates(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->buildExpectedRelayStatesForAgent(
            $gatewayId,
            $this->requireAgentToken($payload),
            isset($payload['wait_seconds']) ? (int)$payload['wait_seconds'] : 0
        ));
    }

    private function handleAgentRelayStateResults(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $results = isset($payload['results']) && is_array($payload['results']) ? (array)$payload['results'] : [];
        $response->success($this->manager()->recordExpectedRelayStateResults(
            $gatewayId,
            $this->requireAgentToken($payload),
            $results
        ));
    }

    private function handleAgentPresence(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();

        $this->requireAgentToken($payload);

        $response->success($this->manager()->recordBrokerPresence(
            $gatewayId,
            isset($payload['status']) ? (string)$payload['status'] : 'disconnected',
            isset($payload['connection_id']) ? (string)$payload['connection_id'] : null,
            isset($payload['reason']) ? (string)$payload['reason'] : null,
            isset($payload['metadata']) && is_array($payload['metadata']) ? (array)$payload['metadata'] : []
        ));
    }

    private function handleAgentSelfserveMachineSignalBindings(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();

        try {
            $response->success((new selfserve_machine_signal())->listEdgeGatewayMachineSignalMonitors(
                $gatewayId,
                $this->requireAgentToken($payload)
            ));
        } catch (\Throwable $exception) {
            $response->error($exception->getMessage(), 400);
        }
    }

    private function handleAgentSelfserveMachineSignal(): void
    {
        global /** @var response $response */ $response;
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();

        try {
            $result = (new selfserve_machine_signal())->recordEdgeGatewaySignal(
                $gatewayId,
                $this->requireAgentToken($payload),
                $payload
            );
            $response->success($result, !empty($result['recorded']) ? 201 : 202);
        } catch (\Throwable $exception) {
            $response->error($exception->getMessage(), 400);
        }
    }

    private function handleBrokerGatewayValidate(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        self::requireParameters(['token']);
        $gatewayId = (int)$this->fromRoute('id');
        $response->success($this->manager()->validateGatewayAgentForBroker($gatewayId, (string)self::getParameter('token')));
    }

    private function handleBrokerGatewayPresence(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->recordBrokerPresence(
            $gatewayId,
            isset($payload['status']) ? (string)$payload['status'] : 'disconnected',
            isset($payload['connection_id']) ? (string)$payload['connection_id'] : null,
            isset($payload['reason']) ? (string)$payload['reason'] : null,
            isset($payload['metadata']) && is_array($payload['metadata']) ? (array)$payload['metadata'] : []
        ));
    }

    private function handleBrokerGatewayBacklog(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->buildBrokerBacklog(
            $gatewayId,
            isset($payload['agent_instance_id']) ? (string)$payload['agent_instance_id'] : null
        ));
    }

    private function handleBrokerGatewayTelemetry(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->recordTelemetryFromBroker($gatewayId, $payload));
    }

    private function handleBrokerOperationEvent(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        $gatewayId = (int)$this->fromRoute('id');
        $operationId = (int)$this->fromRoute('operationId');
        self::requireParameterIntPositive($operationId, 'operationId');
        $payload = self::getParametersAsArray();

        try {
            $response->success($this->operations()->appendBrokerOperationEvent($gatewayId, $operationId, $payload));
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleBrokerOperationComplete(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        $gatewayId = (int)$this->fromRoute('id');
        $operationId = (int)$this->fromRoute('operationId');
        self::requireParameterIntPositive($operationId, 'operationId');
        $payload = self::getParametersAsArray();

        try {
            $response->success($this->operations()->completeBrokerOperation($gatewayId, $operationId, $payload));
        } catch (edge_gateway_operation_exception $exception) {
            $response->error([
                'message' => $exception->getMessage(),
                'error_code' => $exception->errorCode,
            ], $exception->status);
        }
    }

    private function handleBrokerGatewayLogEntry(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        self::requireParameters(['message']);
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->appendGatewayLogEntry(
            $gatewayId,
            (string)self::getParameter('message'),
            isset($payload['level']) ? (string)$payload['level'] : 'INFO',
            isset($payload['stream']) ? (string)$payload['stream'] : 'agent',
            isset($payload['source']) ? (string)$payload['source'] : 'BROKER',
            isset($payload['context']) && is_array($payload['context']) ? (array)$payload['context'] : []
        ));
    }

    private function handleBrokerSelfserveMachineSignal(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        $gatewayId = (int)$this->fromRoute('id');
        $payload = self::getParametersAsArray();

        try {
            $result = (new selfserve_machine_signal())->recordBrokerEdgeGatewaySignal($gatewayId, $payload);
            $response->success($result, !empty($result['recorded']) ? 201 : 202);
        } catch (\Throwable $exception) {
            $response->error($exception->getMessage(), 400);
        }
    }

    private function handleBrokerBrowserStreamValidate(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        self::requireParameters(['token']);
        $response->success($this->manager()->validateBrowserStreamToken((string)self::getParameter('token')));
    }

    private function handleBrokerShellSessionValidate(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        self::requireParameters(['token']);
        $response->success($this->manager()->validateShellSessionToken((string)self::getParameter('token')));
    }

    private function handleBrokerShellSessionOpened(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        self::requireParameters(['token']);
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->markShellSessionOpened(
            (string)self::getParameter('token'),
            isset($payload['connection_id']) ? (string)$payload['connection_id'] : null
        ));
    }

    private function handleBrokerShellSessionClose(): void
    {
        global /** @var response $response */ $response;
        $this->requireBrokerSecret();
        self::requireParameters(['token']);
        $payload = self::getParametersAsArray();
        $response->success($this->manager()->closeShellSessionByToken(
            (string)self::getParameter('token'),
            isset($payload['transcript']) ? (string)$payload['transcript'] : '',
            isset($payload['reason']) ? (string)$payload['reason'] : null,
            [
                'message' => isset($payload['message']) ? (string)$payload['message'] : null,
                'code' => isset($payload['code']) ? (int)$payload['code'] : null,
                'close_code' => isset($payload['close_code']) ? (int)$payload['close_code'] : null,
                'stage' => isset($payload['stage']) ? (string)$payload['stage'] : null,
                'failure_stage' => isset($payload['failure_stage']) ? (string)$payload['failure_stage'] : null,
                'was_clean' => isset($payload['was_clean']) ? (bool)$payload['was_clean'] : null,
                'connection_id' => isset($payload['connection_id']) ? (string)$payload['connection_id'] : null,
                'broker_connection_id' => isset($payload['broker_connection_id']) ? (string)$payload['broker_connection_id'] : null,
                'broker_url' => isset($payload['broker_url']) ? (string)$payload['broker_url'] : null,
                'ws_url' => isset($payload['ws_url']) ? (string)$payload['ws_url'] : null,
                'details' => isset($payload['details']) && is_array($payload['details']) ? (array)$payload['details'] : [],
            ]
        ));
    }

    private function requireGatewayAccess(int $gatewayId): array
    {
        self::requireParameterIntPositive($gatewayId, 'id');
        $gateway = $this->views()->getGateway($gatewayId);
        $this->requireDepartmentAccess((int)$gateway['department_id']);
        return $gateway;
    }

    private function requireAgentToken(array $payload): string
    {
        global /** @var response $response */ $response;
        $token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
        if ($token === '') {
            $response->error('Missing edge gateway agent token', 401);
        }

        return $token;
    }

    private function requireBrokerSecret(): void
    {
        global /** @var response $response */ $response;
        $provided = trim((string)($_SERVER['HTTP_X_EDGE_BROKER_SECRET'] ?? ''));
        if (!$this->manager()->validateBrokerSharedSecret($provided)) {
            $response->error('Invalid edge broker secret', 403);
        }
    }

    private function actorUserId(): ?int
    {
        $user = (new authentication())->get_user();
        return $user ? (int)$user->id : null;
    }

    private function views(): edge_gateway_view_service
    {
        return new edge_gateway_view_service();
    }

    private function registry(): edge_gateway_registry_service
    {
        return new edge_gateway_registry_service();
    }

    private function manager(): edge_gateway_manager
    {
        return new edge_gateway_manager();
    }

    private function operations(): edge_gateway_operation_service
    {
        return new edge_gateway_operation_service($this->manager());
    }

    private function install(): edge_gateway_install_service
    {
        return new edge_gateway_install_service();
    }
}
