package it.enea.cruise.controller;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Table;
import javax.ws.rs.GET;
import javax.ws.rs.POST;

import org.reflections.Reflections;
import static org.reflections.scanners.Scanners.*;

import org.springframework.beans.PropertyAccessor;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import it.enea.cruise.dto.FieldDTO;
import it.enea.cruise.dto.FormDTO;
import it.enea.cruise.dto.MetadataDTO;
import it.enea.cruise.dto.TableMetadataDTO;
import it.enea.cruise.model.structure.DescriptionDTO;
import it.enea.cruise.model.structure.ForeignKey;
import it.enea.cruise.model.structure.TableCommentDTO;
import it.enea.cruise.model.structure.VwTableMetadata;
import it.enea.cruise.model.user.User;
import it.enea.cruise.service.structure.ForeignKeyInterface;
import it.enea.cruise.service.structure.TableMetadataInterface;
import it.enea.cruise.repositories.DTOManager;
import lombok.extern.log4j.Log4j2;

@Controller
@Log4j2
public class StructureController {

	@Autowired
	TableMetadataInterface tmInterface;

	@Autowired
	ForeignKeyInterface fkInterface;

	@Autowired
	DTOManager dtoManager;

	@Autowired
	JdbcTemplate jdbcTemplate;

	@PersistenceContext
	private EntityManager em;

	@Autowired
	ObjectMapper objectMapper;
	
	@Autowired
	UserController userController;
	

	private TableMetadataDTO tDTO;

	@POST
	@PostMapping("/getTableMetadata")
	public ResponseEntity<TableMetadataDTO> getTableMetadata(
			@RequestHeader(name = "Authorization", required = false) String token,
			@RequestHeader(name = "Content-Language", required = false, defaultValue = "it_IT") String language,
			@RequestBody String tableName) {

		tableName = tableName.trim();

		List<VwTableMetadata> tmData1 = tmInterface.findByTableName(tableName);
		List<ForeignKey> fKeyList = fkInterface.findByTableName(tableName);

		Map<String, ForeignKey> keyMap = new HashMap<String, ForeignKey>();

		Object[] parameters = new Object[] {};

		List<MetadataDTO> tmDataDTO = new ArrayList<MetadataDTO>();
		List<String> analysedTables = new ArrayList<String>();

		for (ForeignKey fKey : fKeyList) {
			keyMap.put(fKey.getColumnName(), fKey);
		}

		List<ForeignKey> fKeyListM2M = fkInterface.findDistinctByTableNameLikeOrTableNameLike(tableName + "_%",
				"%_" + tableName);
		
		List<ForeignKey> fKeyListM3M = fkInterface.searchTableNameLike(tableName);
		// Adesso devo lavorare su questi che rappresentano le foreign keys delle
		// relazioni
		// molti a molti.

		for (ForeignKey fKey : fKeyListM2M) {
			String currentTableName = fKey.getTableName();
			String targetTable = "";

			for (ForeignKey fKey2 : fKeyListM2M) {

				if (fKey2.getTableName().equals(currentTableName) && !fKey2.getColumnName().equals(fKey.getColumnName())
						&& !analysedTables.contains(currentTableName)
						&& !fKey2.getForeign_table_name().equals(tableName)) {
					MetadataDTO mDto = new MetadataDTO();
					log.info("Found the keys " + fKey + " - " + fKey2);
					String sql = "SELECT * from " + fKey2.getForeign_table_name();
					log.info("querying for data from " + fKey2.getForeign_table_name());
					analysedTables.add(fKey2.getForeign_table_name());
					targetTable = fKey2.getForeign_table_name();
					SqlRowSet srs = jdbcTemplate.queryForRowSet(sql, parameters);
					List<HashMap<String, Object>> options = new ArrayList<HashMap<String, Object>>();
					String value = null;
					while (srs.next()) {
						Long id = srs.getLong(fKey2.getForeign_column_name());
						try {
							value = srs.getString("description");
						}catch (Exception e) {
							
						}
						if (value == null) {
							String fieldName = fKey2.getForeign_table_name().substring(0,
									fKey2.getForeign_table_name().length() - 1) + "name";
							value = srs.getString(fieldName);
						}
						HashMap<String, Object> opt = new HashMap<String, Object>();
						opt.put("key", id);
						opt.put("value", value);
						options.add(opt);

					}
					mDto.setDataType("Number");
					mDto.setColumnName(currentTableName);
					mDto.setTarget(targetTable);
					mDto.setMultipleOptions(options);
					tmDataDTO.add(mDto);

				}

			}

			log.info(tmDataDTO.toString());
		}

		tDTO = new TableMetadataDTO();

		for (VwTableMetadata tempData : tmData1) {
			if (!((tempData.getColumnDefault() != null && tempData.getColumnDefault().contains("nextval"))
					|| (tempData.getIdentityGeneration() != null
					&& tempData.getIdentityGeneration().contains("ALWAYS")))) {
				MetadataDTO mDto = new MetadataDTO();
				mDto = dtoManager.mtDataDTO(tempData, language);
				if (mDto.getColumnProtected() != null && mDto.getColumnProtected())
					continue;
				if (keyMap.containsKey(tempData.getColumnName())) {
					log.info("Found the key " + tempData.getColumnName());
					ForeignKey key = keyMap.get(tempData.getColumnName());

					mDto.setDataType("Number");
					mDto.setColumnName(key.getColumnName());
					mDto.setRequired(true);
					// adesso devo eseguire una query...
					String sql = "SELECT * from " + key.getForeign_table_name();
					log.info("Querying for data from " + key.getForeign_table_name()
					+ "as oneToOne or oneToMany Relationship");
					SqlRowSet srs = jdbcTemplate.queryForRowSet(sql, parameters);
					List<HashMap<String, Object>> options = new ArrayList<HashMap<String, Object>>();
					String value = "";
					List<String> columnNames = Arrays.asList(srs.getMetaData().getColumnNames());
					while (srs.next()) {
						Long id = srs.getLong(key.getForeign_column_name());
						if (columnNames.contains("description")) {
							value = srs.getString("description");
						} else {
							String fieldName = key.getForeign_table_name().substring(0,
									key.getForeign_table_name().length() - 1) + "name";
							if (columnNames.contains(fieldName)) {
								value = srs.getString(fieldName);
							}
						}
						HashMap<String, Object> opt = new HashMap<String, Object>();
						opt.put("key", id);
						opt.put("value", value);
						options.add(opt);

					}
					mDto.setOptions(options);
				} else
					mDto = dtoManager.mtDataDTO(tempData, language);

				tmDataDTO.add(mDto);

			}

		}

		tDTO.setTableName(tmData1.get(0).getTableName());

		getDescription(tmData1.get(0).getTableDescription(), language);

		tDTO.setMetadata(tmDataDTO);
		log.info(tmDataDTO);
		ObjectMapper objectMapper = new ObjectMapper();
		try {
			log.info(objectMapper.writeValueAsString(tmDataDTO));
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return new ResponseEntity<TableMetadataDTO>(tDTO, HttpStatus.OK);
	}

	@GET
	@GetMapping("listManagedEntities")
	public ResponseEntity<List<String>> listManagedEneities(
			@RequestHeader(name = "Authorization", required = false) String token,
			@RequestHeader(name = "Content-Language", required = false, defaultValue = "it_IT") String language) {
		List<String> result = new ArrayList<String>();
		result.add("flows");
		result.add("users");
		result.add("communities");
		return new ResponseEntity<List<String>>(result, HttpStatus.OK);
	}

	

	@POST
	@PostMapping("forms")
	@Transactional
	public ResponseEntity<String> postForms(
			@RequestHeader(name = "Authorization", required = false) String token,
			@RequestHeader(name = "Content-Language", required = false, defaultValue = "it_IT") String language,
			@RequestBody FormDTO body) throws Exception {

		log.info("Body: " + body);

		User user = userController.getLocalUserFromToken(token);
		
		if(user == null) {
			new ResponseEntity<String>("User not valid", HttpStatus.FORBIDDEN);
		}
		Object entity = new Object();

		// Dovrei cercare tutte le classi che potrebbero gestire la mia tabella...
		Reflections reflections = new Reflections("it.enea.cruise.model");
		Set<String> allEntities = reflections.get(TypesAnnotated.with(Entity.class));

		for (String entityTypeString : allEntities) {
			Class<?> et = reflections.forClass(entityTypeString, null);

			Class etClass = et.getComponentType();
			Table table = et.getAnnotation(Table.class);
			log.info(table.name());
			if (table != null && table.name().equals(body.getCode())) {
				try {
					entity = et.newInstance();

					PropertyAccessor myAccessor = PropertyAccessorFactory.forDirectFieldAccess(entity);

					for (FieldDTO f : body.getFields()) {
						if (f.getName() != null && f.getMultipleOptions() == null) {
							List<String> values = f.getValue();
							//log.info("Values ha size:" + values.size());
							if (values.size() < 2) {
								log.info("Field name: " + f.getName());
								Class<?> property = myAccessor.getPropertyType(f.getName());
								String propName = property.getName();
								if (values.size() == 1)
									myAccessor.setPropertyValue(f.getName(), f.getValue().get(0));
							} else {
								log.info("Errore: un campo non multipleOptions ha un numero di valori maggiore di 1 ");
								throw new Exception(
										"Errore: un campo non multipleOptions ha un numero di valori maggiore di 1");
							}
						} else if (f.getName() != null && f.getMultipleOptions() != null) {
							log.info("found multiple options");
							for (String entityTypeString2 : allEntities) {
								Class<?> et2 = reflections.forClass(entityTypeString, null);

								Class etClass2 = et.getComponentType();
								Table table2 = et.getAnnotation(Table.class);
								log.info(table.name());
								if (table2 != null && table2.name().equals(f.getName())) {

									try {
										entity = et2.newInstance();

									} catch (Exception e) {

									}
								}
							}
						}
					}
					Class<?> property = myAccessor.getPropertyType("user");
					if(property != null) {
						Class entityClass = entity.getClass();
						Method setUserMethod = entity.getClass().getMethod("setUser", User.class);
						log.info(entity);
						log.info(setUserMethod);
						log.info(user.getUserid().toString());
						if (setUserMethod != null) setUserMethod.invoke(entity, user);
						Method setUserIdMethod = entity.getClass().getMethod("setUserid", Integer.class);
						Integer userID = new Integer(user.getUserid().intValue());
						if (setUserIdMethod != null) setUserIdMethod.invoke(entity, userID);
					}
					log.info(entity.toString());
					break;
				} catch (InstantiationException | IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}

		}
		try {
			em.merge(entity);
		}catch (Exception e) {
			log.info(e.getLocalizedMessage());
		}
		return new ResponseEntity<String>("Ricevuto", HttpStatus.OK);
	}

	public void getDescription(String cComment, String language) {
		if (cComment == null) return;
		TableCommentDTO tComment = new TableCommentDTO();
		try {

			if (cComment.contains("JSON:")) {
				cComment = cComment.split("JSON:")[1];


				tComment = objectMapper.readValue(cComment, TableCommentDTO.class);

				if(tComment.getDescriptionOrdered().containsKey(language)) {
					DescriptionDTO td  = tComment.getDescriptionOrdered().get(language);
					tDTO.setTableDescription(td.getShortDescription());
					tDTO.setTableDescriptionLong(td.getLongDescription());
				}else if (tComment.getDescriptionOrdered().get("en") != null){
					DescriptionDTO td  = tComment.getDescriptionOrdered().get("en");
					tDTO.setTableDescription(td.getShortDescription());
					tDTO.setTableDescriptionLong(td.getLongDescription());
				}else {
					tDTO.setTableDescription("No value");
					tDTO.setTableDescriptionLong("No value");

				}
			}else {
				log.info(tComment.toString());
				if (cComment != null) {
					log.info("working with: " + cComment);
					String[] result = cComment.contains("|") ? cComment.split(" \\| ") : null;
					if (result != null) {
						if (language.contains("it")) {
							tDTO.setTableDescription(result[2]);
							tDTO.setTableDescriptionLong(result[3]);
						} else {
							if (result.length > 1) {
								tDTO.setTableDescription(result[0]);
								tDTO.setTableDescriptionLong(result[1]);
							} else
								tDTO.setTableDescription(result[0]);
							tDTO.setTableDescriptionLong(result[1]);
						}
					} else
						tDTO.setTableDescription("No value");
					tDTO.setTableDescriptionLong("No value");
				}
			}
		} catch (JsonProcessingException  e) {
			log.info(e.getLocalizedMessage());
		}
	}

}
