package com.ctrip.framework.apollo.portal.spi.ctrip;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserService;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class CtripUserService implements UserService {
  private RestTemplate restTemplate;
  private List<String> searchUserMatchFields;
  private ParameterizedTypeReference<Map<String, List<UserServiceResponse>>> responseType;
  private PortalConfig portalConfig;

  public CtripUserService(PortalConfig portalConfig) {
    this.portalConfig = portalConfig;
    this.restTemplate = new RestTemplate(clientHttpRequestFactory());
    this.searchUserMatchFields =
        Lists.newArrayList("empcode", "empaccount", "displayname", "c_name", "pinyin");
    this.responseType = new ParameterizedTypeReference<Map<String, List<UserServiceResponse>>>() {
    };
  }

  private ClientHttpRequestFactory clientHttpRequestFactory() {
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(portalConfig.connectTimeout());
    factory.setReadTimeout(portalConfig.readTimeout());

    return factory;
  }

  @Override
  public List<UserInfo> searchUsers(String keyword, int offset, int limit) {
    UserServiceRequest request = assembleSearchUserRequest(keyword, offset, limit);

    HttpEntity<UserServiceRequest> entity = new HttpEntity<>(request);
    ResponseEntity<Map<String, List<UserServiceResponse>>> response =
        restTemplate.exchange(portalConfig.userServiceUrl(), HttpMethod.POST, entity, responseType);

    if (!response.getBody().containsKey("result")) {
      return Collections.emptyList();
    }

    List<UserInfo> result = Lists.newArrayList();
    result.addAll(
        response.getBody().get("result").stream().map(this::transformUserServiceResponseToUserInfo)
            .collect(Collectors.toList()));

    return result;
  }

  @Override
  public UserInfo findByUserId(String userId) {
    List<UserInfo> userInfoList = this.findByUserIds(Lists.newArrayList(userId));
    if (CollectionUtils.isEmpty(userInfoList)) {
      return null;
    }
    return userInfoList.get(0);
  }

  public List<UserInfo> findByUserIds(List<String> userIds) {
    UserServiceRequest request = assembleFindUserRequest(Lists.newArrayList(userIds));

    HttpEntity<UserServiceRequest> entity = new HttpEntity<>(request);
    ResponseEntity<Map<String, List<UserServiceResponse>>> response =
        restTemplate.exchange(portalConfig.userServiceUrl(), HttpMethod.POST, entity, responseType);

    if (!response.getBody().containsKey("result")) {
      return Collections.emptyList();
    }

    List<UserInfo> result = Lists.newArrayList();
    result.addAll(
        response.getBody().get("result").stream().map(this::transformUserServiceResponseToUserInfo)
            .collect(Collectors.toList()));

    return result;
  }

  private UserInfo transformUserServiceResponseToUserInfo(UserServiceResponse userServiceResponse) {
    UserInfo userInfo = new UserInfo();
    userInfo.setUserId(userServiceResponse.getEmpaccount());
    userInfo.setName(userServiceResponse.getDisplayname());
    userInfo.setEmail(userServiceResponse.getEmail());
    return userInfo;
  }

  UserServiceRequest assembleSearchUserRequest(String keyword, int offset, int limit) {
    Map<String, Object> query = Maps.newHashMap();
    Map<String, Object> multiMatchMap = Maps.newHashMap();
    multiMatchMap.put("fields", searchUserMatchFields);
    multiMatchMap.put("operator", "and");
    multiMatchMap.put("query", keyword);
    multiMatchMap.put("type", "best_fields");
    query.put("multi_match", multiMatchMap);

    return assembleUserServiceRequest(query, offset, limit);
  }

  UserServiceRequest assembleFindUserRequest(List<String> userIds) {
    Map<String, Object>
        query =
        ImmutableMap.of("filtered", ImmutableMap
            .of("filter", ImmutableMap.of("terms", ImmutableMap.of("empaccount", userIds))));

    return assembleUserServiceRequest(query, 0, userIds.size());
  }

  private UserServiceRequest assembleUserServiceRequest(Map<String, Object> query, int offset,
                                                        int limit) {
    UserServiceRequest request = new UserServiceRequest();
    request.setAccess_token(portalConfig.userServiceAccessToken());

    UserServiceRequestBody requestBody = new UserServiceRequestBody();
    requestBody.setIndexAlias("itdb_emloyee");
    requestBody.setType("emloyee");
    request.setRequest_body(requestBody);

    Map<String, Object> queryJson = Maps.newHashMap();
    requestBody.setQueryJson(queryJson);

    queryJson.put("query", query);

    queryJson.put("from", offset);
    queryJson.put("size", limit);

    return request;
  }


  static class UserServiceRequest {
    private String access_token;
    private UserServiceRequestBody request_body;

    public String getAccess_token() {
      return access_token;
    }

    public void setAccess_token(String access_token) {
      this.access_token = access_token;
    }

    public UserServiceRequestBody getRequest_body() {
      return request_body;
    }

    public void setRequest_body(
        UserServiceRequestBody request_body) {
      this.request_body = request_body;
    }
  }

  static class UserServiceRequestBody {
    private String indexAlias;
    private String type;
    private Map<String, Object> queryJson;

    public String getType() {
      return type;
    }

    public void setType(String type) {
      this.type = type;
    }

    public String getIndexAlias() {
      return indexAlias;
    }

    public void setIndexAlias(String indexAlias) {
      this.indexAlias = indexAlias;
    }

    public Map<String, Object> getQueryJson() {
      return queryJson;
    }

    public void setQueryJson(Map<String, Object> queryJson) {
      this.queryJson = queryJson;
    }
  }

  static class UserServiceResponse {
    private String empaccount;
    private String displayname;
    private String email;

    public String getEmpaccount() {
      return empaccount;
    }

    public void setEmpaccount(String empaccount) {
      this.empaccount = empaccount;
    }

    public String getDisplayname() {
      return displayname;
    }

    public void setDisplayname(String displayname) {
      this.displayname = displayname;
    }

    public String getEmail() {
      return email;
    }

    public void setEmail(String email) {
      this.email = email;
    }
  }

}
