class StackCreatorService {

    public StackV4Response createStack(User user, Workspace workspace, StackV4Request stackRequest, boolean distroxRequest) {
        long start = System.currentTimeMillis();
        String stackName = stackRequest.getName();

        measure(() ->
                validateRecipeExistenceOnInstanceGroups(stackRequest.getInstanceGroups(), workspace.getId()),
                LOGGER,
                "Check that recipes do exist took {} ms");

        measure(() ->
                ensureStackDoesNotExists(stackName, workspace),
                LOGGER,
                "Stack does not exist check took {} ms");

        Stack stackStub = measure(() ->
                converterUtil.convert(stackRequest, Stack.class),
                LOGGER,
                "Stack request converted to stack took {} ms for stack {}", stackName);
        stackStub.setWorkspace(workspace);
        stackStub.setCreator(user);
        String platformString = stackStub.getCloudPlatform().toLowerCase();

        MDCBuilder.buildMdcContext(stackStub);
        Stack savedStack;
        try {
            Blueprint blueprint = measure(() ->
                determineBlueprint(stackRequest, workspace),
                LOGGER,
                "Stack request converted to stack took {} ms for stack {}", stackName);

            Future<StatedImage> imgFromCatalogFuture = determineImageCatalog(stackName, platformString, stackRequest, blueprint, user, workspace);

            savedStack = transactionService.required(() -> {
                Stack stack = measure(() ->
                        stackDecorator.decorate(stackStub, stackRequest, user, workspace),
                        LOGGER,
                        "Decorate Stack with data took {} ms");

                DetailedEnvironmentResponse environment =  measure(() ->
                        ThreadBasedUserCrnProvider.doAsInternalActor(() ->
                                environmentClientService.getByCrn(stack.getEnvironmentCrn())),
                        LOGGER,
                        "Get Environment from Environment service took {} ms");
                Credential credential = credentialConverter.convert(environment.getCredential());
                CloudCredential cloudCredential = credentialToCloudCredentialConverter.convert(credential);


                if (stack.getOrchestrator() != null && stack.getOrchestrator().getApiEndpoint() != null) {
                    measure(() -> stackService.validateOrchestrator(stack.getOrchestrator()),
                            LOGGER,
                            "Validate orchestrator took {} ms");
                }
                stack.setUseCcm(environment.getTunnel().useCcm());
                stack.setTunnel(environment.getTunnel());

                if (stackRequest.getCluster() != null) {

                    measure(() -> setStackType(stack, blueprint),
                        LOGGER,
                        "Set stacktype for stack object took {} ms");

                    measure(() -> clusterCreationService.validate(
                            stackRequest.getCluster(), cloudCredential, stack, user, workspace, environment, distroxRequest),
                            LOGGER,
                            "Validate cluster rds and autotls took {} ms");
                }

                measure(() -> fillInstanceMetadata(stack),
                        LOGGER,
                        "Fill up instance metadata took {} ms");


                StatedImage imgFromCatalog = measure(() -> getImageCatalog(imgFromCatalogFuture),
                        LOGGER,
                        "Select the correct image took {} ms");
                Stack newStack = measure(() -> stackService.create(
                            stack, platformString, imgFromCatalog, user, workspace, Optional.ofNullable(stackRequest.getResourceCrn())),
                            LOGGER,
                        "Save the remaining stack data took {} ms"
                        );

                try {
                    LOGGER.info("Create cluster enity in the database {} ms with name {}.", stackName);
                    long clusterSaveStart = System.currentTimeMillis();
                    createClusterIfNeed(user, stackRequest, newStack, stackName, blueprint, environment.getParentEnvironmentCloudPlatform());
                    LOGGER.info("Cluster save took {} ms", System.currentTimeMillis() - clusterSaveStart);
                } catch (CloudbreakImageCatalogException | IOException | TransactionExecutionException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
                Stack withSharedServicesIfNeeded = measure(() -> prepareSharedServiceIfNeed(newStack),
                        LOGGER,
                        "Shared service preparation if required took {} ms with name {}.", stackName);
                measure(() -> assignOwnerRoleOnDataHub(user, stackRequest, newStack),
                        LOGGER,
                        "assignOwnerRoleOnDataHub to stack took {} ms with name {}.", stackName);
                return withSharedServicesIfNeeded;
            });
        } catch (TransactionExecutionException e) {
            stackUnderOperationService.off();
            if (e.getCause() instanceof DataIntegrityViolationException) {
                String msg = String.format("Error with resource [%s], error: [%s]", APIResourceType.STACK,
                        getProperSqlErrorMessage((DataIntegrityViolationException) e.getCause()));
                throw new BadRequestException(msg);
            }
            throw new TransactionRuntimeExecutionException(e);
        }

        StackV4Response response = measure(() -> converterUtil.convert(savedStack, StackV4Response.class),
                LOGGER, "Stack response has been created for stack took {} ms with name {}", stackName);

        LOGGER.info("Generated stack response after creation: {}", JsonUtil.writeValueAsStringSilentSafe(response));

        FlowIdentifier flowIdentifier = measure(() ->
                flowManager.triggerProvisioning(savedStack.getId()),
                LOGGER,
                "Stack triggerProvisioning took {} ms with name {}", stackName);
        response.setFlowIdentifier(flowIdentifier);

        metricService.submit(STACK_PREPARATION, System.currentTimeMillis() - start);

        return response;
    }

}