<?php

namespace routes;

use classes\authentication;
use modules\selfserve\classes\selfserve_config_versioning;
use modules\selfserve\classes\selfserve_studio_graph;
use modules\selfserve\classes\selfserve_wash_flow;
use objects\logs_o;
use traits\route_t;

class departmentSelfserveStudioRoute
{
    use route_t;

    public function run(): void
    {
        $this->get('/department/selfserve/studio/graph', function (): void {
            global $response;
            $user = $this->requireStudioUser('list_department_selfserve_config_versions');
            self::requireParameters(['department']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId, ['view_all_department_selfserve_config_versions']);

            $service = new selfserve_studio_graph();
            (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'GET_STUDIO_GRAPH', 'Fetched self-serve studio graph');
            $response->success($service->buildGraph($departmentId, (int)$user->id, $this->studioPermissions()));
        }, [
            'list_department_selfserve_config_versions' => 'View the all-in-one self-serve studio graph',
            'view_all_department_selfserve_config_versions' => 'View the all-in-one self-serve studio graph across departments',
        ]);

        $this->put('/department/selfserve/studio/graph', function (): void {
            global $response;
            $user = $this->requireStudioUser('edit_department_selfserve_config_versions');
            self::requireParameters(['department']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $payload = self::getParametersAsArray();
                $service = new selfserve_studio_graph();
                $graph = $service->applyGraphSave($departmentId, $payload, (int)$user->id, $this->studioPermissions());
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SAVE_STUDIO_GRAPH', 'Saved self-serve studio graph');
                $response->success($graph);
            } catch (\RuntimeException $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'edit_department_selfserve_config_versions' => 'Create, update, delete, connect, and reorder self-serve studio graph objects',
        ]);

        $this->put('/department/selfserve/studio/layout', function (): void {
            global $response;
            $user = $this->requireStudioUser('edit_department_selfserve_config_versions');
            self::requireParameters(['department', 'layout']);
            self::requireType(self::getParameter('layout'), self::TYPE_ARRAY());
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $layout = (new selfserve_studio_graph())->saveLayout($departmentId, (int)$user->id, (array)self::getParameter('layout'));
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SAVE_STUDIO_LAYOUT', 'Saved self-serve studio layout');
                $response->success($layout);
            } catch (\RuntimeException $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'edit_department_selfserve_config_versions' => 'Save canvas-only self-serve studio layout',
        ]);

        $this->put('/department/selfserve/studio/virtual-hardware', function (): void {
            global $response;
            $user = $this->requireStudioUser('edit_department_selfserve_config_versions');
            self::requireParameters(['department', 'operation']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $payload = self::getParametersAsArray();
                $graph = (new selfserve_studio_graph())->applyVirtualHardwareOperation(
                    $departmentId,
                    $payload,
                    (int)$user->id,
                    $this->studioPermissions()
                );
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SAVE_STUDIO_VIRTUAL_HARDWARE', 'Saved self-serve studio virtual hardware');
                $response->success($graph);
            } catch (\RuntimeException $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'edit_department_selfserve_config_versions' => 'Generate and edit studio-only virtual hardware',
        ]);

        $this->post('/department/selfserve/studio/validate', function (): void {
            global $response;
            $user = $this->requireStudioUser('edit_department_selfserve_config_versions');
            self::requireParameters(['department']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            $validation = (new selfserve_studio_graph())->validatePayload($departmentId, self::getParametersAsArray());
            (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'VALIDATE_STUDIO_GRAPH', 'Validated self-serve studio graph');
            $response->success($validation);
        }, [
            'edit_department_selfserve_config_versions' => 'Validate the self-serve studio graph',
        ]);

        $this->post('/department/selfserve/studio/simulate', function (): void {
            global $response;
            $user = $this->requireStudioUser('list_department_selfserve_config_versions');
            self::requireParameters(['department', 'lane_id', 'reg']);
            $departmentId = (int)self::getParameter('department');
            $laneId = (int)self::getParameter('lane_id');
            self::requireParameterIntPositive($laneId, 'lane_id');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $result = (new selfserve_studio_graph())->simulateGraph(
                    $departmentId,
                    self::getParametersAsArray(),
                    (int)$user->id,
                    $this->studioPermissions()
                );
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SIMULATE_STUDIO_GRAPH', 'Simulated self-serve studio graph');
                $response->success($result);
            } catch (\Throwable $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'list_department_selfserve_config_versions' => 'Run the self-serve studio simulator',
        ]);

        $this->post('/department/selfserve/studio/path-outcomes/stream', function (): void {
            $user = $this->requireStudioUser('list_department_selfserve_config_versions');
            self::requireParameters(['department']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            ini_set('display_errors', '0');
            ini_set('html_errors', '0');
            header('Content-Type: application/x-ndjson; charset=utf-8');
            header('Cache-Control: no-cache, no-transform');
            header('X-Accel-Buffering: no');

            $emit = static function (array $event): void {
                echo json_encode($event, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
                if (function_exists('ob_flush')) {
                    @ob_flush();
                }
                @flush();
            };

            try {
                $result = (new selfserve_studio_graph())->projectPathOutcomes(
                    $departmentId,
                    self::getParametersAsArray(),
                    (int)$user->id,
                    $this->studioPermissions(),
                    static function (array $partial) use ($emit): void {
                        $emit(['type' => 'progress', 'data' => $partial]);
                    }
                );
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'PROJECT_STUDIO_PATH_OUTCOMES', 'Projected self-serve studio path outcomes');
                $emit(['type' => 'complete', 'data' => $result]);
            } catch (\Throwable $exception) {
                $emit(['type' => 'error', 'message' => $exception->getMessage()]);
            }
            exit;
        }, [
            'list_department_selfserve_config_versions' => 'Stream grouped self-serve studio question path outcome progress',
        ]);

        $this->post('/department/selfserve/studio/path-outcomes', function (): void {
            global $response;
            $user = $this->requireStudioUser('list_department_selfserve_config_versions');
            self::requireParameters(['department']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $result = (new selfserve_studio_graph())->projectPathOutcomes(
                    $departmentId,
                    self::getParametersAsArray(),
                    (int)$user->id,
                    $this->studioPermissions()
                );
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'PROJECT_STUDIO_PATH_OUTCOMES', 'Projected self-serve studio path outcomes');
                $response->success($result);
            } catch (\Throwable $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'list_department_selfserve_config_versions' => 'Project grouped self-serve studio question path outcomes',
        ]);

        $this->post('/department/selfserve/studio/path-confirmations', function (): void {
            global $response;
            $user = $this->requireStudioUser('edit_department_selfserve_config_versions');
            self::requireParameters(['department', 'path_signature']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $payload = self::getParametersAsArray();
                $action = strtolower(trim((string)($payload['action'] ?? 'confirm')));
                $service = new selfserve_studio_graph();
                $result = in_array($action, ['delete', 'reset', 'clear'], true)
                    ? $service->resetPathConfirmation($departmentId, $payload)
                    : $service->confirmPathOutcome($departmentId, $payload, (int)$user->id);
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'CONFIRM_STUDIO_PATH', 'Updated self-serve studio path confirmation');
                $response->success($result);
            } catch (\RuntimeException $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'edit_department_selfserve_config_versions' => 'Confirm or reset projected self-serve studio answer paths',
        ]);

        $this->post('/department/selfserve/studio/publish', function (): void {
            global $response;
            $user = $this->requireStudioUser('publish_department_selfserve_config_versions');
            self::requireParameters(['department']);
            $departmentId = (int)self::getParameter('department');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $published = (new selfserve_config_versioning())->publishDraft($departmentId, (int)$user->id);
                $validation = (new selfserve_studio_graph())->validatePayload($departmentId);
                $published['warnings'] = (array)($validation['warnings'] ?? []);
                $published['validation'] = $validation;
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'PUBLISH_STUDIO_GRAPH', 'Published self-serve studio graph');
                $response->success($published);
            } catch (\RuntimeException $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'publish_department_selfserve_config_versions' => 'Publish the self-serve studio draft',
        ]);

        $this->post('/department/selfserve/studio/rollback', function (): void {
            global $response;
            $user = $this->requireStudioUser('rollback_department_selfserve_config_versions');
            self::requireParameters(['department', 'target_version_id']);
            $departmentId = (int)self::getParameter('department');
            $targetVersionId = (int)self::getParameter('target_version_id');
            self::requireParameterIntPositive($targetVersionId, 'target_version_id');
            $this->assertDepartmentAccess($user, $departmentId);

            try {
                $rolledBack = (new selfserve_config_versioning())->rollbackToVersion($departmentId, $targetVersionId, (int)$user->id);
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'ROLLBACK_STUDIO_GRAPH', 'Rolled back self-serve studio graph');
                $response->success($rolledBack);
            } catch (\RuntimeException $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'rollback_department_selfserve_config_versions' => 'Rollback the self-serve studio draft to an earlier version',
        ]);

        $this->post('/department/selfserve/studio/gateway-action', function (): void {
            global $response;
            $user = $this->requireStudioUser('modules_shelly_config');
            self::requireParameters(['department', 'gateway_id', 'action']);
            $departmentId = (int)self::getParameter('department');
            $gatewayId = (int)self::getParameter('gateway_id');
            self::requireParameterIntPositive($gatewayId, 'gateway_id');
            $this->assertDepartmentAccess($user, $departmentId);

            $action = strtolower((string)self::getParameter('action'));
            $confirmed = filter_var(self::getParameter('confirm'), FILTER_VALIDATE_BOOLEAN);
            if (in_array($action, ['uninstall', 'rotate_credentials'], true) && $confirmed !== true) {
                $response->error('This gateway action requires explicit confirmation.', 428);
            }

            try {
                $payload = self::getParametersAsArray();
                $result = (new selfserve_studio_graph())->runGatewayAction($departmentId, $gatewayId, $action, $payload, (int)$user->id);
                (new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'GATEWAY_STUDIO_ACTION', 'Ran self-serve studio gateway action: ' . $action);
                $response->success($result);
            } catch (\Throwable $exception) {
                $response->error($exception->getMessage(), 422);
            }
        }, [
            'modules_shelly_config' => 'Run permission-gated self-serve studio edge gateway actions',
        ]);
    }

    private function requireStudioUser(string $permission): object
    {
        global $response;
        $this->requirePermission($permission);
        $user = (new authentication())->get_user();
        if (!$user) {
            $response->error('Invalid session', 400);
        }

        return $user;
    }

    /**
     * @param array<int,string> $bypassPermissions
     */
    private function assertDepartmentAccess(object $user, int $departmentId, array $bypassPermissions = []): void
    {
        $authorizedDepartmentIds = $user->getGroup()->getDepartments();
        if (in_array($departmentId, $authorizedDepartmentIds, true)) {
            return;
        }

        foreach ($bypassPermissions as $permission) {
            if ($this->hasPermission($permission)) {
                return;
            }
        }

        $this->forbidDepartmentAccess($departmentId, $bypassPermissions);
    }

    /**
     * @return array<string,bool>
     */
    private function studioPermissions(): array
    {
        return [
            'can_view' => $this->hasPermission('list_department_selfserve_config_versions'),
            'can_edit' => $this->hasPermission('edit_department_selfserve_config_versions'),
            'can_publish' => $this->hasPermission('publish_department_selfserve_config_versions'),
            'can_rollback' => $this->hasPermission('rollback_department_selfserve_config_versions'),
            'can_simulate' => $this->hasPermission('list_department_selfserve_vehicle_conditions'),
            'can_add_department_lane' => $this->hasPermission('add_department_lane'),
            'can_edit_department_lane' => $this->hasPermission('edit_department_lane'),
            'modules_shelly_config' => $this->hasPermission('modules_shelly_config'),
            'can_manage_gateways' => $this->hasPermission('modules_shelly_config'),
            'can_run_gateway_destructive_actions' => $this->hasPermission('modules_shelly_config'),
            'can_run_live_lane_actions' => $this->hasPermission('modules_selfserve_sessions_force_stop'),
        ];
    }
}
