package itn.let.solr.search.impl;

import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SpellCheckResponse.Suggestion;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.params.CommonParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ui.ModelMap;

import itn.let.solr.search.service.SearchService;

public class SearchServiceImpl implements InitializingBean, SearchService {

	Logger log = LoggerFactory.getLogger(this.getClass());

	@Value("${Globals.Solr.url}")
	private String SERVER_URL;
	
	private Set<String> introFieldSet;
	private Set<String> policyFieldSet;
	private Set<String> safetyFieldSet;
	private Set<String> noticeFieldSet;
	private Set<String> communityFieldSet;
	private Set<String> infoFieldSet;
	
	private Set<String> fileFieldSet;
	@SuppressWarnings("unused")
	private Set<String> boardFieldSet;
	@SuppressWarnings("unused")
	private Set<String> webpageFieldSet;
	@SuppressWarnings("unused")
	private Set<String> itn_BoardFieldSet;
	@SuppressWarnings("unused")
	private Set<String> itn_ContentFieldSet;
	
	private Map<String, Set<String>> fieldMap = new HashMap<String, Set<String>>();

	private final String REGEX = "^[1-9a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣| *]+$";
	private final String SPECIAL_REGEX = "[^ㄱ-ㅎ\uAC00-\uD7A3xfe0-9a-zA-Z\\s]";

	private Pattern pattern = Pattern.compile(REGEX);

	public SearchServiceImpl(){
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		fieldMap.put(SOLR_CORE.INTRO.getValue(), introFieldSet);
		fieldMap.put(SOLR_CORE.POLICY.getValue(), policyFieldSet);
		fieldMap.put(SOLR_CORE.SAFETY.getValue(), safetyFieldSet);
		fieldMap.put(SOLR_CORE.NOTICE.getValue(), noticeFieldSet);
		fieldMap.put(SOLR_CORE.COMMUNITY.getValue(), communityFieldSet);
		fieldMap.put(SOLR_CORE.INFO.getValue(), infoFieldSet);
		
		/*fieldMap.put(SOLR_CORE.FILE.getValue(), fileFieldSet);
		fieldMap.put(SOLR_CORE.WEBPAGE.getValue(), webpageFieldSet);
		fieldMap.put(SOLR_CORE.BOARD.getValue(), boardFieldSet);
		fieldMap.put(SOLR_CORE.ITN_BOARD.getValue(), itn_BoardFieldSet);
		fieldMap.put(SOLR_CORE.ITN_CONTENT.getValue(), itn_ContentFieldSet);*/
	}

	@Override
	public Set<String> suggest(Map<String, Object> commandMap) throws Exception {
		String q = (String)commandMap.get("q");
		if (StringUtils.isBlank(q)) {
			return Collections.emptySet();
		}
		q = URLDecoder.decode(q, "UTF-8");
		Set<String> resultList = new LinkedHashSet<String>();

		SolrQuery query = new SolrQuery();
	    query.setParam(CommonParams.QT, "/suggest");
		query.setQuery(q);

		String serverUrl = SERVER_URL.endsWith("/") ? SERVER_URL : SERVER_URL + "/";
		for(SOLR_CORE sc : SOLR_CORE.values()){
			SolrClient client = null;
			try {
				client = new HttpSolrClient(serverUrl+sc.getValue());
				QueryResponse rsp = client.query(query);
				if (rsp != null) {
					List<Suggestion> suggestions = rsp.getSpellCheckResponse().getSuggestions();
					if( CollectionUtils.isNotEmpty(suggestions) ){
						for(Suggestion s : suggestions){
							resultList.addAll(s.getAlternatives());
						}
					}
				}
			} catch (SolrServerException e) {
				e.printStackTrace();
			} finally {
				IOUtils.closeQuietly(client);
			}
		}
		log.debug("{} - {}", serverUrl, resultList);
		return resultList;
	}


	@Override
	public void search(Map<String, Object> commandMap, ModelMap model) throws Exception {
		String q = (String)commandMap.get("q");
		if ( StringUtils.isBlank(q) ) {
			q = (String)commandMap.get("q2");
		}
		if (StringUtils.isNotBlank(q)) {
			Matcher matcher = pattern.matcher(q);
			if( matcher.find() ) {
				commandMap.put("srchwrd", q);
			}
		}

		Map<String, List<Map<String, Object>>> resultMap = new HashMap<String, List<Map<String, Object>>>();
		Map<String, Long> resultCntMap = new HashMap<String, Long>();
		long totalCount = 0;
		for (SOLR_CORE sc : SOLR_CORE.values()) {
			resultCntMap.put(sc.getValue(), new Long(0));
			resultMap.put(sc.getValue(), Collections.<Map<String, Object>>emptyList());
			List<Map<String, Object>> resultList = getResultList(sc, fieldMap.get(sc.getValue()), commandMap);
			long numFound = 0;
			if ( !CollectionUtils.isEmpty(resultList) ) {
				Map<String, Object> result = resultList.get(0);
				numFound = NumberUtils.toLong(result.get("numFound").toString());
				totalCount += numFound;
				resultCntMap.put(sc.getValue(), numFound);
				resultMap.put(sc.getValue(), resultList);
			}

			if ( sc.getValue().equals((String)commandMap.get("rangeView")) ) {
			}
		}
		model.addAttribute("resultMap", resultMap);
		model.addAttribute("resultCntMap", resultCntMap);
		model.addAttribute("totalCount", totalCount);

	}

	private List<Map<String, Object>> getResultList(SOLR_CORE core, Set<String> fieldSet, Map<String, Object> commandMap){
		List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();

		String serverUrl = SERVER_URL.endsWith("/") ? SERVER_URL : SERVER_URL + "/";
		HttpSolrClient client = null;
		SolrQuery query = makeQuery(core, commandMap, fieldSet);
		log.debug("{}{}", serverUrl, query);
		try {
			client = new HttpSolrClient(serverUrl+core.getValue());
			client.setConnectionTimeout(1000);
			QueryResponse rsp = client.query(query);
			Iterator<SolrDocument> iter = rsp.getResults().iterator();
			long numFound = rsp.getResults().getNumFound();
			while (iter.hasNext()) {
				Map<String, Object> resultMap = new HashMap<>();
				SolrDocument resultDoc = iter.next();
				for(String field : fieldSet){
					resultMap.put(field, resultDoc.getFieldValue(field));
				}
				Object id = resultDoc.getFieldValue("id");
				Map<String, Map<String, List<String>>> highlighting = rsp.getHighlighting();
				if( highlighting.get(id.toString()) != null ){
					Map<String, List<String>> highlightSnippetMap = highlighting.get(id);
					String hlFl = StringUtils.defaultString((String)commandMap.get("hl.fl"), "text");
					List<String> highlightSnippets = highlightSnippetMap.get(hlFl);
					if( CollectionUtils.isNotEmpty(highlightSnippets) ){
						resultMap.put("hl", highlightSnippets.get(0));
					}
					List<String> nttSjHl = highlightSnippetMap.get("nttSj");
					if( CollectionUtils.isNotEmpty(nttSjHl) ){
						resultMap.put("nttSjHl", nttSjHl.get(0));
					}
				}
				resultMap.put("numFound", numFound);
				resultList.add(resultMap);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(client);
		}
		return resultList;
	}

	private SolrQuery makeQuery(SOLR_CORE core, Map<String, Object> commandMap, Set<String> fieldSet){
		SolrQuery query = new SolrQuery();
		String q = StringUtils.defaultString((String)commandMap.get("q"));
		q = q.replaceAll(SPECIAL_REGEX, "");
		String re = (String)commandMap.get("re");
		String sdate = (String)commandMap.get("sdate");
		String edate = (String)commandMap.get("edate");
		String date = (String)commandMap.get("date");

		int pageIndex = 1;
		String rangeView = (String)commandMap.get("rangeView");
		if (core.getValue().equals(rangeView)) {
			String pi = (String)commandMap.get("pageIndex");
			if (StringUtils.isNotBlank(pi)) {
				pageIndex = NumberUtils.toInt(pi, 1);
			}
		}
		int pageUnit = 10;
		int start = (pageIndex -1) * pageUnit;
		query.setStart(start);
		StringBuilder _q = new StringBuilder();
		if( "Y".equals(re) ){
			String prevQ = (String)commandMap.get("prevQ");
			if( StringUtils.isNotBlank(q) && StringUtils.isNotBlank(prevQ) ){
				_q.append(q).append(" AND ").append(prevQ);
			}
			else if( StringUtils.isNotBlank(prevQ) ){
				_q.append(prevQ);
			}
			else if( StringUtils.isNotBlank(q) ){
				_q.append(q);
			}
			else{
				_q.append("*:*");
			}
		}
		else{
			if( !StringUtils.isNotBlank(q) ){
				_q.append("*:*");
			}
			else{
				_q.append(q);
			}
		}

		if( !"*:*".equals(_q.toString()) ){
			_q.insert(0, "*");
			_q.append("*");
		}
		if (StringUtils.isNotBlank(sdate) && StringUtils.isNotBlank(edate)) {
			query.addFilterQuery("registDt:["+sdate+" TO "+edate+"]");
		}
		if (StringUtils.isNotBlank(date)) {
			Date today = new Date();
			String e = DateFormatUtils.format(today, "yyyy-mm-dd");
			String s = "";
			if ("1d".equals(date)) {
				s = DateFormatUtils.format(DateUtils.addDays(today, -1), "yyyy-mm-dd");
			}
			else if ("1w".equals(date)) {
				s = DateFormatUtils.format(DateUtils.addDays(today, -7), "yyyy-mm-dd");
			}
			else if ("1m".equals(date)) {
				s = DateFormatUtils.format(DateUtils.addMonths(today, -1), "yyyy-mm-dd");
			}
			else if ("3m".equals(date)) {
				s = DateFormatUtils.format(DateUtils.addMonths(today, -3), "yyyy-mm-dd");
			}
			else if ("6m".equals(date)) {
				s = DateFormatUtils.format(DateUtils.addMonths(today, -6), "yyyy-mm-dd");
			}
			query.addFilterQuery("registDt:["+s+" TO "+e+"]");				
		}
		String categoryData = (String)commandMap.get("categoryData");
		log.debug("categoryData : {}", categoryData);
		if (categoryData != null) {
			String[] data = StringUtils.split(categoryData, ",");
			for (String c : data) {
				query.addFilterQuery("category:" + c);
			}
		}

		String hlFl = StringUtils.defaultString((String)commandMap.get("hl.fl"), "text");
		String hlFragsize = StringUtils.defaultString((String)commandMap.get("hl.fragsize"), "140");
		query.setQuery(_q.toString());
		query.setHighlight(true).setHighlightSnippets(1);
		query.setParam("hl.fl", hlFl, "nttSj");
		query.setParam("hl.fragsize", hlFragsize);
		Map<String, ORDER> m = getSortMap(commandMap, fieldSet);
		if( m != null ) {
			for (Map.Entry<String, ORDER> entry : m.entrySet()) {
				query.addSort(entry.getKey(), entry.getValue());
			}
		}
		return query;
	}

	private Map<String, ORDER> getSortMap(Map<String, Object> commandMap, Set<String> fieldSet) {
		Map<String, ORDER> sortMap = new HashMap<String, ORDER>();
		String s = (String)commandMap.get("s");
		if( StringUtils.isNotBlank(s) ){
			String[] data = StringUtils.split(s, "^");
			for (String d : data) {
				String[] sortField = StringUtils.split(d, "|");
				if ( !"default".equals(sortField[0]) ) {
					if (fieldSet != null && fieldSet.contains(sortField[0])) {
						String field = sortField[0];
						ORDER order = null;
						if (sortField[1].equals("asc")) {
							order = SolrQuery.ORDER.asc;
						}
						else {
							order = SolrQuery.ORDER.desc;
						}
						sortMap.put(field, order);
					}
				}
			}
		}
		return sortMap;
	}

	public Set<String> getFileFieldSet() {
		return fileFieldSet;
	}

	public void setFileFieldSet(Set<String> fileFieldSet) {
		this.fileFieldSet = fileFieldSet;
	}

	public void setBoardFieldSet(Set<String> boardFieldSet) {
		this.boardFieldSet = boardFieldSet;
	}

	public void setWebpageFieldSet(Set<String> webpageFieldSet) {
		this.webpageFieldSet = webpageFieldSet;
	}

	public void setItn_BoardFieldSet(Set<String> itn_BoardFieldSet) {
		this.itn_BoardFieldSet = itn_BoardFieldSet;
	}

	public void setItn_ContentFieldSet(Set<String> itn_ContentFieldSet) {
		this.itn_ContentFieldSet = itn_ContentFieldSet;
	}

	public Set<String> getIntroFieldSet() {
		return introFieldSet;
	}

	public void setIntroFieldSet(Set<String> introFieldSet) {
		this.introFieldSet = introFieldSet;
	}

	public Set<String> getPolicyFieldSet() {
		return policyFieldSet;
	}

	public void setPolicyFieldSet(Set<String> policyFieldSet) {
		this.policyFieldSet = policyFieldSet;
	}

	public Set<String> getSafetyFieldSet() {
		return safetyFieldSet;
	}

	public void setSafetyFieldSet(Set<String> safetyFieldSet) {
		this.safetyFieldSet = safetyFieldSet;
	}

	public Set<String> getNoticeFieldSet() {
		return noticeFieldSet;
	}

	public void setNoticeFieldSet(Set<String> noticeFieldSet) {
		this.noticeFieldSet = noticeFieldSet;
	}

	public Set<String> getCommunityFieldSet() {
		return communityFieldSet;
	}

	public void setCommunityFieldSet(Set<String> communityFieldSet) {
		this.communityFieldSet = communityFieldSet;
	}

	public Set<String> getInfoFieldSet() {
		return infoFieldSet;
	}

	public void setInfoFieldSet(Set<String> infoFieldSet) {
		this.infoFieldSet = infoFieldSet;
	}

}
