package ru.yandex.direct.web.entity.useractionlog;

import java.util.Collections;
import java.util.Optional;

import javax.annotation.ParametersAreNonnullByDefault;

import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions;
import graphql.schema.GraphQLSchema;
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility;
import io.leangen.graphql.GraphQLSchemaGenerator;
import io.leangen.graphql.generator.mapping.common.EnumMapper;
import org.dataloader.DataLoaderRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import ru.yandex.direct.env.EnvironmentType;
import ru.yandex.direct.grid.model.admin.GraphqlSchemaProvider;
import ru.yandex.direct.grid.model.mapper.InputEnumMapper;
import ru.yandex.direct.libs.graphql.GraphqlHelper;
import ru.yandex.direct.libs.graphql.model.GraphqlRequest;
import ru.yandex.direct.libs.graphql.model.GraphqlResponse;

import static graphql.ExecutionInput.newExecutionInput;

@Lazy
@ParametersAreNonnullByDefault
@Service
public class UserActionLogGraphQLService implements GraphqlSchemaProvider {
    private static final Logger logger = LoggerFactory.getLogger(UserActionLogGraphQLService.class);
    private final GraphQL graphQL;
    private final DataLoaderRegistry dataLoaderRegistry;
    private final GraphqlHelper graphqlHelper;

    @Autowired
    public UserActionLogGraphQLService(EnvironmentType environmentType, UserActionLogService userActionLogService,
                                       UserActionLogDataLoaderRegistry userActionLogDataLoaderRegistry,
                                       GraphqlHelper graphqlHelper) {
        this.graphqlHelper = graphqlHelper;
        GraphQLSchemaGenerator graphQLSchemaGenerator = new GraphQLSchemaGenerator();
        GraphQLSchema schema = graphQLSchemaGenerator
                .withTypeMappers((conf, current) ->
                        current.replace(EnumMapper.class, m -> new InputEnumMapper()))
                .withOperationsFromSingleton(userActionLogService)
                .withBasePackages("ru.yandex.direct.useractionlog",
                        "ru.yandex.direct.grid.model")
                .generate();

        if (environmentType.isProductionOrPrestable() || EnvironmentType.TESTING.equals(environmentType)) {
            schema = GraphQLSchema.newSchema(schema)
                    .fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)
                    .build();
        }

        DataLoaderDispatcherInstrumentationOptions options =
                DataLoaderDispatcherInstrumentationOptions.newOptions();
        DataLoaderDispatcherInstrumentation dispatcherInstrumentation =
                new DataLoaderDispatcherInstrumentation(options);

        this.dataLoaderRegistry = userActionLogDataLoaderRegistry;
        this.graphQL = GraphQL.newGraphQL(schema)
                .instrumentation(dispatcherInstrumentation)
                .build();
    }

    public GraphqlResponse executeGraphQL(GraphqlRequest req) {
        ExecutionInput executionInput = newExecutionInput()
                .dataLoaderRegistry(dataLoaderRegistry)
                .query(req.getQuery())
                .context(dataLoaderRegistry)
                .operationName(req.getOperationName())
                .variables(Optional.ofNullable(req.getVariables()).orElse(Collections.emptyMap()))
                .build();

        ExecutionResult executionResult = graphQL.execute(executionInput);

        if (!executionResult.getErrors().isEmpty()) {
            logger.error("Got some errors: {}", executionResult.getErrors());
        }
        return graphqlHelper.formatResponse(executionResult, false);
    }

    @Override
    public GraphQL getGraphql() {
        return graphQL;
    }

    @Override
    public String getGraphqlApiName() {
        return "user_action_log";
    }
}
