package com.remy.graphql.builder;

import com.remy.common.module.CommentDTO;
import com.remy.common.module.GoodsDTO;
import com.remy.common.module.OrderDTO;
import com.remy.common.module.UserDTO;
import com.remy.graphql.evn.RestAPIEnv;
import graphql.kickstart.execution.context.DefaultGraphQLContext;
import graphql.kickstart.execution.context.GraphQLContext;
import graphql.kickstart.servlet.context.DefaultGraphQLServletContext;
import graphql.kickstart.servlet.context.DefaultGraphQLWebSocketContext;
import graphql.kickstart.servlet.context.GraphQLServletContextBuilder;
import org.apache.commons.lang3.StringUtils;
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.concurrent.CompletableFuture.supplyAsync;

@Configuration
public class GraphqlContextBuilder implements GraphQLServletContextBuilder {

    @Autowired
    RestAPIEnv apiEnv;

    @Override
    public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        return DefaultGraphQLServletContext.createServletContext(buildDataLoaderRegistry(), null)
                .with(httpServletRequest)
                .with(httpServletResponse)
                .build();
    }

    @Override
    public GraphQLContext build(Session session, HandshakeRequest handshakeRequest) {
        return DefaultGraphQLWebSocketContext.createWebSocketContext(buildDataLoaderRegistry(), null)
                .with(session)
                .with(handshakeRequest)
                .build();
    }

    @Override
    public GraphQLContext build() {
        return new DefaultGraphQLContext(buildDataLoaderRegistry(), null);
    }

    private DataLoaderRegistry buildDataLoaderRegistry() {
        DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry();
        dataLoaderRegistry.register("goodsDataLoader", goodsDataLoader());
        dataLoaderRegistry.register("userDataLoader", userDataLoader());
        dataLoaderRegistry.register("orderGoodsDataLoader", orderGoodsDataLoader());
        dataLoaderRegistry.register("orderUserDataLoader", orderUserDataLoader());
        dataLoaderRegistry.register("userAttentionsDataLoader", userAttentionsDataLoader());
        dataLoaderRegistry.register("orderCommentsDataLoader", orderCommentsDataLoader());
        return dataLoaderRegistry;
    }

    private DataLoader<String, GoodsDTO> goodsDataLoader() {
        return new DataLoader<>(
                goodsSerialNos -> supplyAsync(
                        () -> new RestTemplate().exchange(
                                apiEnv.getGoodsEndpoint() + "query?serialNos={goodsSerialNos}",
                                HttpMethod.GET,
                                new HttpEntity(null),
                                new ParameterizedTypeReference<List<GoodsDTO>>() {
                                }, StringUtils.join(goodsSerialNos, ",")).getBody()
                ));
    }

    private DataLoader<Long, UserDTO> userDataLoader() {
        return new DataLoader<>(
                userIDs -> supplyAsync(
                        () -> new RestTemplate().exchange(
                                apiEnv.getUsersEndpoint() + "queryByIds?userIds={userIDs}",
                                HttpMethod.GET,
                                new HttpEntity(null),
                                new ParameterizedTypeReference<List<UserDTO>>() {
                                }, StringUtils.join(userIDs, ",")).getBody()
                ));
    }

    private DataLoader<String, List<OrderDTO>> orderGoodsDataLoader() {
        return new DataLoader<>(
                goodsSerialNos -> supplyAsync(
                        () -> goodsSerialNos.stream().map(goodsSerialNo ->
                                new RestTemplate().exchange(
                                        apiEnv.getOrdersEndpoint() + "query?goodsSerialNo={goodsSerialNo}",
                                        HttpMethod.GET,
                                        new HttpEntity(null),
                                        new ParameterizedTypeReference<List<OrderDTO>>() {
                                        }, goodsSerialNo).getBody())
                                .collect(Collectors.toList())));
    }

    private DataLoader<Long, List<OrderDTO>> orderUserDataLoader() {
        return new DataLoader<>(
                userIDs -> supplyAsync(
                        () -> userIDs.stream().map(userID ->
                                new RestTemplate().exchange(
                                        apiEnv.getOrdersEndpoint() + "query?userID={userID}",
                                        HttpMethod.GET,
                                        new HttpEntity(null),
                                        new ParameterizedTypeReference<List<OrderDTO>>() {
                                        }, userID).getBody())
                                .collect(Collectors.toList())));
    }

    private DataLoader<List<Long>, List<UserDTO>> userAttentionsDataLoader() {
        return new DataLoader<>(
                attentionIds -> supplyAsync(
                        () -> attentionIds.stream().map(attentionIdList ->
                                new RestTemplate().exchange(
                                        apiEnv.getUsersEndpoint() + "queryByIds?userIds={userIDs}",
                                        HttpMethod.GET,
                                        new HttpEntity(null),
                                        new ParameterizedTypeReference<List<UserDTO>>() {
                                        }, StringUtils.join(attentionIdList, ",")).getBody())
                                .collect(Collectors.toList())));
    }

    private DataLoader<String, CommentDTO> orderCommentsDataLoader() {
        return new DataLoader<>(
                orderNos -> supplyAsync(
                        () -> orderNos.stream().map(orderNo ->
                                new RestTemplate().getForObject(apiEnv.getCommentsEndpoint() + orderNo, CommentDTO.class))
                                .collect(Collectors.toList())
                )
        );
    }
}
