package com.nbclass.activity.service.impl;

import java.io.IOException;
import java.util.*;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.nbclass.activity.model.Content;
import com.nbclass.activity.model.ContentTags;
import com.nbclass.activity.service.ContentService;
import com.nbclass.activity.service.ElasticSearchService;
import com.nbclass.exception.ParameterException;
import com.nbclass.exception.ServiceException;

import lombok.extern.slf4j.Slf4j;

/**
 * 案例内容ES操作
 * @author Leon
 * @datetime 2020年6月1日 下午3:02:00
 */
@Slf4j
@Service
public class ElasticSearchServiceImpl implements ElasticSearchService {

	@Resource
    private RestHighLevelClient restHighLevelClient;

	@Resource
	private ContentService contentService;

	private static final String es_index_content = "es-cases-content";


	/**
	 * 检查索引是否存在
	 * @throws IOException
	 */
    private boolean existIndex() throws IOException{
    	GetIndexRequest request = new GetIndexRequest().indices(es_index_content);
    	@SuppressWarnings("deprecation")
    	boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    	return exists;
    }

	// 创建索引
	public void createIndex(){
    	try {
			// 检查索引是否存在
	        if(existIndex())return;

	    	CreateIndexRequest indexRequest = new CreateIndexRequest(es_index_content);
	        CreateIndexResponse indexResponse = restHighLevelClient.indices().create(indexRequest, RequestOptions.DEFAULT);
	        log.info("创建索引, 返回值=>{}, 确认状态=>{}", indexResponse.index(), indexResponse.isAcknowledged());
    	}catch (Exception e) {
    		log.info("创建索引异常", e);
    	}
    }

    // 删除索引
    public void deleteIndex(){
    	try {
	    	DeleteIndexRequest indexRequest = new DeleteIndexRequest(es_index_content);
	        AcknowledgedResponse delete = restHighLevelClient.indices().delete(indexRequest, RequestOptions.DEFAULT);
	        log.info("删除索引, 返回值=>{}, 确认状态=>{}", delete.isFragment(), delete.isAcknowledged());
    	}catch (Exception e) {
    		log.info("删除索引异常", e);
    	}
    }

	// 创建或者更新文档
	@Override
	public void merge(Content entity) {
        try {
        	if(entity!=null && entity.getTagList()!=null && entity.getTagList().size()>0) {
        		String tags = "";
        		for(ContentTags tag : entity.getTagList()) {
        			if(StringUtils.isBlank(tag.getTagName()))continue;
        			tags += (StringUtils.isBlank(tags)?"":",")+tag.getTagName();
        		}
        		entity.setTags(tags); // 将标签组到字符串里，便于搜索。
        	}

        	createIndex(); // 判断索引是否存在，不存在创建索引

        	// 判断document是否存在，存在修改，不存在新增
        	GetRequest getRequest = new GetRequest(es_index_content, String.valueOf(entity.getId()));
        	boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
        	if(!exists) {
        		// 创建文档索引
        		IndexRequest indexRequest = new IndexRequest(es_index_content);
        		indexRequest.id(String.valueOf(entity.getId())).source(JSON.toJSONString(entity), XContentType.JSON);
        		@SuppressWarnings("unused")
    			IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
    			log.info("添加文档，新建ES索引成功，id = " + entity.getId());
        	}else {
        		// 修改文档索引
        		UpdateRequest updateRequest = new UpdateRequest(es_index_content, String.valueOf(entity.getId()));
                updateRequest.doc(JSON.toJSONString(entity), XContentType.JSON);
                @SuppressWarnings("unused")
    			UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
                log.info("修改文档，重建ES索引成功，id = " + entity.getId());
        	}
		} catch (IOException e) {
			log.error("添加文档，新建ES索引失败，id = " + entity.getId(), e);
			throw new ServiceException("案例更新ES索引失败");
		}
	}

	@Override
	public void deleteDocByIds(List<Long> ids) {
		if(ids == null || ids.isEmpty()){
			return;
		}
		try {
			for (Long id : ids) {
				// DeleteRequest
				DeleteRequest deleteRequest = new DeleteRequest(es_index_content, String.valueOf(id));
				@SuppressWarnings("unused")
				DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
			}
		 } catch (IOException e) {
			log.error("删除案例ES索引失败", e);
			throw new ServiceException("删除案例ES索引失败");
		}
	}

	// matchQuery：会将搜索词分词，再与目标查询字段进行匹配，若分词中的任意一个词与目标字段匹配上，则可查询到。
	// termQuery：不会对搜索词进行分词处理，而是作为一个整体与目标字段进行匹配，若完全匹配，则可查询到。
	@Override
	public void deleteDocByQuery(Content entity) {
		/*if(entity.getType()!=null) {
			throw new ParameterException("案例类型不能为空");
		}*/
		try {
			DeleteByQueryRequest request = new DeleteByQueryRequest(es_index_content);
	        request.setConflicts("proceed");	// 发生冲突即略过
	        //request.setQuery(QueryBuilders.termQuery("type", entity.getType()));
			request.setQuery(QueryBuilders.matchAllQuery());
	        BulkByScrollResponse bulkResponse = restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);

	        TimeValue timeTaken = bulkResponse.getTook();
	        boolean timedOut = bulkResponse.isTimedOut();
	        long totalDocs = bulkResponse.getTotal();
	        long updatedDocs = bulkResponse.getUpdated();
	        long deletedDocs = bulkResponse.getDeleted();
	        long batches = bulkResponse.getBatches();
	        long noops = bulkResponse.getNoops();
	        long versionConflicts = bulkResponse.getVersionConflicts();
	        log.info("根据案例类型删除文档，花费时间：" + timeTaken + ",是否超时：" + timedOut + ",总文档数：" + totalDocs + ",更新数：" +
	        		updatedDocs + ",删除数：" + deletedDocs + ",批量次数：" + batches + ",跳过数：" + noops + ",冲突数：" + versionConflicts);
		} catch (IOException e) {
			log.error("根据案例类型删除文档异常", e);
		}
	}

	@Override
    public boolean existDoc(Long id){
    	boolean exists = false;
    	GetRequest getRequest = new GetRequest(es_index_content, String.valueOf(id));
        try {
        	exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
		} catch (IOException e) {
			e.printStackTrace();
		}
        return exists;
    }

	// 获取文档
	@Override
	public Content findById(Long id) {
		// GetRequest
        GetRequest getRequest = new GetRequest(es_index_content, String.valueOf(id));
        // 查询ES
        GetResponse getResponse;
        Content content = null;
		try {
			getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
			content = JSON.parseObject(getResponse.getSourceAsString(), Content.class);
			if(content == null) {
				content = contentService.findById(id);
			}
			if(content!=null && content.getId()!=null && content.getId()>0) {
				content.setTags(null);
				content.setImages(null);
			}
		} catch (IOException e) {
			log.error("从ES查询数据失败", e);
			throw new ServiceException("从ES查询数据失败");
		}
		return content;
	}

	/**
	 * 从数据库查询
	 * @return
	 */
	private JSONObject getListByMySQL(Integer pageNum, Integer pageSize, String type, String keyWord, String tagIds, String sourceIds) {
		JSONObject result = new JSONObject();
		Content param = new Content();
		param.setType(type);
		param.setTitle(keyWord);
		param.setTags(tagIds);
		param.setFromids(sourceIds);
		PageHelper.startPage(pageNum, pageSize);
        List<Content> dataList = contentService.getListByFront(param);
        PageInfo<Content> pages = new PageInfo<>(dataList);
        if(dataList!=null && dataList.size()>0) {
        	for (Content entity : dataList) {
        		// 清空一些前端用不上的字段
	        	entity.setTags(null);
	        	entity.setImages(null);
	        	entity.setImgList(null);
			}
        }

        result.put("total", pages.getTotal()); // 总记录数
        result.put("rows", dataList); // 数据体
        int totalPage = (int)(pages.getTotal() % pageSize==0 ? pages.getTotal()/pageSize : pages.getTotal()/pageSize+1);
        result.put("totalPage", totalPage); // 总页数
		return result;
	}

	@Override
	public Map<String, Object> getList(Integer pageNum, Integer pageSize, String type, String keyWord, String tagIds, String sourceIds) {
		// 封装Map参数返回
		Map<String, Object> result = new HashMap<>();
		try {
			// 选择标签或来源过滤时，从数据库查询记录
			/*if(StringUtils.isNotBlank(tagIds) || StringUtils.isNotBlank(sourceIds)) {
				return getListByMySQL(pageNum, pageSize, type, keyWord, tagIds, sourceIds);
			}*/

			SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	        // 分页采用简单的from + size分页，适用数据量小的，了解更多分页方式可自行查阅资料
	        searchSourceBuilder.from((pageNum - 1) * pageSize); // 从0开始
	        searchSourceBuilder.size(pageSize);

	        // 查询条件，只有查询关键字不为空才带查询条件
	        if (StringUtils.isNoneBlank(keyWord)) {
	        //	QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "desc");
	        //	searchSourceBuilder.query(queryBuilder);
	        }

	        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
			boolQueryBuilder.must(QueryBuilders.termQuery("visibility", 1));
	        /*if(StringUtils.isNotBlank(type)) { // 案例类型
        		boolQueryBuilder.must(QueryBuilders.termQuery("type", type));
        		if (StringUtils.isNoneBlank(keyWord)){
	        		BoolQueryBuilder keywordQuery = QueryBuilders.boolQuery();
	        		keywordQuery.should(QueryBuilders.matchQuery("title", keyWord));
	        		keywordQuery.should(QueryBuilders.matchQuery("desct", keyWord));
	        		keywordQuery.should(QueryBuilders.matchQuery("fromName", keyWord));
	        		keywordQuery.should(QueryBuilders.matchQuery("tags", keyWord));
	        		keywordQuery.should(QueryBuilders.matchQuery("content_text", keyWord));
	        		boolQueryBuilder.must(keywordQuery);
        		}
        	}else {
        		if (StringUtils.isNoneBlank(keyWord)){
	        		boolQueryBuilder.should(QueryBuilders.matchQuery("title", keyWord)); //.boost(3) // 给name字段更高的权重
		            boolQueryBuilder.should(QueryBuilders.matchQuery("desct", keyWord));   // description 默认权重 1
		            boolQueryBuilder.should(QueryBuilders.matchQuery("fromName", keyWord));
		            boolQueryBuilder.should(QueryBuilders.matchQuery("tags", keyWord));
		            boolQueryBuilder.should(QueryBuilders.matchQuery("content_text", keyWord));
		            boolQueryBuilder.minimumShouldMatch(1); // 至少一个should条件满足
        		}
        	}*/
			if (StringUtils.isNoneBlank(keyWord)){
				BoolQueryBuilder keywordQuery = QueryBuilders.boolQuery();
				keywordQuery.should(QueryBuilders.matchQuery("title", keyWord));
				keywordQuery.should(QueryBuilders.matchQuery("desct", keyWord));
				//keywordQuery.should(QueryBuilders.matchQuery("fromName", keyWord));
				keywordQuery.should(QueryBuilders.matchQuery("tags", keyWord));
				keywordQuery.should(QueryBuilders.matchQuery("content_text", keyWord));
				boolQueryBuilder.must(keywordQuery);
			}
	        //选择标签或来源过滤
	        if(StringUtils.isNotBlank(tagIds)){
				List<String> tagIdList = Arrays.asList(tagIds.split(","));
				for(String tagId:tagIdList) {
					boolQueryBuilder.must(QueryBuilders.termQuery("tagList.tid", tagId));
				}
			}
			/*if(StringUtils.isNotBlank(sourceIds)){
				List<String> sourIdList = Arrays.asList(sourceIds.split(","));
				for(String sourId:sourIdList) {
					boolQueryBuilder.must(QueryBuilders.termQuery("fromid", sourId));
				}
			}*/
	        searchSourceBuilder.query(boolQueryBuilder);


	        // 排序，根据ID倒叙
	        searchSourceBuilder.sort("id", SortOrder.DESC);
	        // SearchRequest
	        SearchRequest searchRequest = new SearchRequest();
	        searchRequest.source(searchSourceBuilder);
	        // 查询ES
	        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
	        SearchHits hits = searchResponse.getHits();
	        // 获取总数
	        Long total = hits.getTotalHits().value;
	        // 遍历封装列表对象
	        List<Content> dataList = new ArrayList<>();
	        SearchHit[] searchHits = hits.getHits();
	        for (SearchHit searchHit : searchHits) {
	        	Content entity = JSON.parseObject(searchHit.getSourceAsString(), Content.class);
	        	// 清空一些前端用不上的字段
	        	entity.setTags(null);
	        	entity.setImages(null);
	        	entity.setImgList(null);
	        	dataList.add(entity);
	        }
	        result.put("total", total); // 总记录数
	        result.put("rows", dataList); // 数据体
	        int totalPage = (int)(total % pageSize==0 ? total/pageSize : total/pageSize+1);
	        result.put("totalPage", totalPage); // 总页数
		}catch (Exception e) {
			log.error("从es查询数据列表异常", e);
		}
		return result;
	}

}
