package it.enea.cruise.service.parametricQuery;


import java.sql.Timestamp;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import it.enea.cruise.model.parametricQuery.LocalQueryParameter;
import it.enea.cruise.model.parametricQuery.ParametricQuery;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;

@Component
@Log4j2
public class ParametricQueryManager {

	@Autowired
	private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
	
	@Autowired
	LocalQueryParameterInterface lqpInterface;
	
	private ParametricQuery pQuery = new ParametricQuery();
	
	public ResultWithMetadata executeQuery(ParametricQuery pq, HashMap<String, String> queryParam, String language) {
		return executeQuery(pq, (Map<String, String>) queryParam, language);
	}
	
	public ResultWithMetadata executeQuery(ParametricQuery pq, Map<String, String> queryParam, String language) {
		ResultWithMetadata resultWithMetadata = new ResultWithMetadata();
		List<Map<String,Object>> result = null;
		List<HashMap<String, String>> parameterMetadata = new ArrayList<HashMap<String, String>>();			
			
		MapSqlParameterSource sqlParam = new MapSqlParameterSource();
	
		List<LocalQueryParameter> paramsName = pq.getParameters();
		
		String value = queryParam.get("userid");
		sqlParam.addValue("userid", value, Types.INTEGER);
		
		String sql = pq.getSql();
		
		log.info(sql);
			
		for (LocalQueryParameter lqp : paramsName) {
			
			value = queryParam.get(lqp.getName());
			if (lqp.getType().toUpperCase() == "TIMESTAMP") {
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
			    Date parsedDate = null;
				try {
					parsedDate = dateFormat.parse(value);
				} catch (ParseException e) {
					e.printStackTrace();
				}
			    Timestamp timestamp = new java.sql.Timestamp(parsedDate.getTime());
			    sqlParam.addValue(lqp.getName(), timestamp, getSqlType(lqp.getType()));
			}
			else {
				sqlParam.addValue(lqp.getName(), value, getSqlType(lqp.getType()));
			}
		}
		List<String> list = null;
		List<LocalQueryParameter> tmdata;
		Map<String,List<String>> resultList = getResultList(pq);
		for (String key : resultList.keySet()) {
			list = resultList.get(key);
			for (String columnName : list) {
				HashMap<String, String> metadata = new HashMap<String, String>();
				tmdata = lqpInterface.findAllByName(columnName);
				for (LocalQueryParameter lqpTemp : tmdata) {
					metadata.put("name", lqpTemp.getName());
					metadata.put("description", lqpTemp.getDescription(language));
					metadata.put("type", lqpTemp.getType());
					metadata.put("example", lqpTemp.getExample());
					metadata.put("unitOfMeasure", lqpTemp.getUnitofmeasure());
					
				}
				
				resultWithMetadata.setMetadata(metadata);
			}
		
		}
		pq.setParameterMetadata(parameterMetadata);
		
		try{
			result = namedParameterJdbcTemplate.queryForList(
				sql,
				sqlParam
				);
			resultWithMetadata.setResult(result);
			log.info("Found: " + result.size() + " lines");
		}catch(Exception e) {
			System.out.println(e.getLocalizedMessage());
		}		
		return resultWithMetadata;
	}
	/*
	 * Questa funzione estrae la lista dei possibili risultati di una query usando i dati di esempio di ogni parametro.
	 * In questo modo, e' possibile estrarre la lista delle colonne restituite dalla query stessa.
	 */
	@Cacheable(value = "parametricqueriesmap", key = "#pq.name")
	public Map<String, List<String>> getResultList(ParametricQuery pq){
		if (pq.getQueryType().equals("sql")) {
			String sql = pq.getSql();
			List<String> result = new ArrayList<String>();
			List<LocalQueryParameter> lqp = pq.getParameters();
			MapSqlParameterSource sqlParam = new MapSqlParameterSource();
				
			sqlParam.addValue("userid", "1", Types.INTEGER);
			for (LocalQueryParameter lp : lqp) {
				sqlParam.addValue(lp.getName(), lp.getExample(), getSqlType(lp.getType()));
			}
			// Questo e' lento perche' deve eseguire una query differente per ogni funzione. 
			SqlRowSet rset = namedParameterJdbcTemplate.queryForRowSet(sql, sqlParam);
	
			int colCount = rset.getMetaData().getColumnCount();
			rset.first();
			for (int i = 1; i <= colCount; i++) {
				result.add(rset.getMetaData().getColumnLabel(i));
			}
			Map<String, List<String>> resultMap = new HashMap<String, List<String>>();
			String tableName = rset.getMetaData().getTableName(1) == ""  ? "noName" : rset.getMetaData().getTableName(1);
			resultMap.put(tableName, result);
			return resultMap;
		}
		else {
			log.info("Non e' una query SQL!");
			RestTemplate restTemplate = new RestTemplate();
		    Object result = restTemplate.getForObject(pq.getSql(), Object.class);
			return new HashMap<String, List<String>>();
			
		}
		
	}
	
	public static int getSqlType(String name) {
	    switch (name.toUpperCase().trim()) {
	    //case "DATE":
	    //    return Types.DATE;
	    case "DATE":
	        return Types.VARCHAR;
	    case "INTEGER":
	        return Types.INTEGER;
	    case "CHAR":
	        return Types.CHAR;
	    case "VARCHAR":
	        return Types.VARCHAR;
	    case "LONGVARCHAR":
	        return Types.LONGVARCHAR ;
	    case "BIGINT":
	        return Types.BIGINT;
	    case "ARRAY":
	        return Types.ARRAY;
	    case "STRING":
	    	return Types.VARCHAR;
	    case "INTERVAL":
	    	return Types.VARCHAR;
//	    case Types.FLOAT:
//	        return "FLOAT";
//	    case Types.REAL:
//	        return "REAL";
//	    case Types.DOUBLE:
//	        return "DOUBLE";
//	    case Types.NUMERIC:
//	        return "NUMERIC";
//	    case Types.DECIMAL:
//	        return "DECIMAL";
//	    case Types.TIME:
//	        return "TIME";
	    case "TIMESTAMP":
	        return Types.TIMESTAMP;
//	    case Types.BINARY:
//	        return "BINARY";
//	    case Types.VARBINARY:
//	        return "VARBINARY";
//	    case Types.LONGVARBINARY:
//	        return "LONGVARBINARY";
//	    case Types.NULL:
//	        return "NULL";
//	    case Types.OTHER:
//	        return "OTHER";
//	    case Types.JAVA_OBJECT:
//	        return "JAVA_OBJECT";
//	    case Types.DISTINCT:
//	        return "DISTINCT";
//	    case Types.STRUCT:
//	        return "STRUCT";
//	    case Types.ARRAY:
//	        return "ARRAY";
//	    case Types.BLOB:
//	        return "BLOB";
//	    case Types.CLOB:
//	        return "CLOB";
//	    case Types.REF:
//	        return "REF";
//	    case Types.DATALINK:
//	        return "DATALINK";
//	    case Types.BOOLEAN:
//	        return "BOOLEAN";
//	    case Types.ROWID:
//	        return "ROWID";
//	    case Types.NCHAR:
//	        return "NCHAR";
//	    case Types.NVARCHAR:
//	        return "NVARCHAR";
//	    case Types.LONGNVARCHAR:
//	        return "LONGNVARCHAR";
//	    case Types.NCLOB:
//	        return "NCLOB";
//	    case Types.SQLXML:
//	        return "SQLXML";
	    }

	    return 0;
	}
	
@ToString
	public class ResultWithMetadata {
		List<Map<String,String>> metadata;
		List<Map<String,Object>> result;
		
		
		public ResultWithMetadata (){
			metadata = new ArrayList<Map<String,String>>();
			//result = new ArrayList<Map<String,Object>>();
		}
		
		public List<Map<String, String>> getMetadata() {
			return metadata;
		}

		public void setMetadata(Map<String,String> metaData) {
			metadata.add(metaData);
//			for (int i = 1; i<= sqlMetaData.getColumnCount(); i++) {
//				Map<String,String> tmp = new HashMap<String,String>();
//				tmp.put("name", sqlMetaData.getColumnName(i));
//				tmp.put("type", sqlMetaData.getColumnTypeName(i));
//				
//			}
		}

		public List<Map<String, Object>> getResult() {
			return result;
		}

		public void setResult(List<Map<String, Object>> result) {
			this.result = result;
		}
	}
	
}
