package qa.qcri.aidr.manager.controller;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.apache.log4j.Logger;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import qa.qcri.aidr.common.values.UsageType;
import qa.qcri.aidr.manager.dto.AidrCollectionTotalDTO;
import qa.qcri.aidr.manager.dto.CollectionBriefInfo;
import qa.qcri.aidr.manager.dto.CollectionDetailsInfo;
import qa.qcri.aidr.manager.dto.CollectionSummaryInfo;
import qa.qcri.aidr.manager.dto.TaggerCrisisType;
import qa.qcri.aidr.manager.exception.AidrException;
import qa.qcri.aidr.manager.persistence.entities.Collection;
import qa.qcri.aidr.manager.persistence.entities.UserAccount;
import qa.qcri.aidr.manager.service.CollectionCollaboratorService;
import qa.qcri.aidr.manager.service.CollectionLogService;
import qa.qcri.aidr.manager.service.CollectionService;
import qa.qcri.aidr.manager.service.CrisisTypeService;
import qa.qcri.aidr.manager.service.TaggerService;
import qa.qcri.aidr.manager.util.CollectionStatus;
import qa.qcri.aidr.manager.util.GeoRestrictionType;
import qa.qcri.aidr.manager.util.JsonDataValidator;
import qa.qcri.aidr.manager.util.SMS;

import com.fasterxml.jackson.databind.ObjectMapper;

@Controller
@RequestMapping("public/collection")
public class PublicController extends BaseController{
	private static Logger logger = Logger.getLogger(PublicController.class);
	
	@Autowired
	private CollectionService collectionService;

	@Autowired
	private TaggerService taggerService;

	@Autowired
	private CollectionLogService collectionLogService;

	@Autowired
	private CrisisTypeService crisisTypeService;
	
	@Autowired 
	private CollectionCollaboratorService collaboratorService;
	
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
	}

    @RequestMapping(value = "/updateGeo.action", method = RequestMethod.POST)
    @Consumes(MediaType.APPLICATION_JSON)
    @ResponseBody
    public Map<String,Object> update(@RequestBody final String jsonCollection ) throws Exception {
        logger.info("updateGeo.action JSON:" + jsonCollection);

        if(jsonCollection == null){

            return getUIWrapper(false);
        }
        if(!JsonDataValidator.isValidEMSCJson(jsonCollection)){

            return getUIWrapper(false);
        }

        JSONParser parser = new JSONParser();
        Object obj = parser.parse(jsonCollection);

        JSONObject jsonObject = (JSONObject) obj;

        String geoString = (String)jsonObject.get("geo");
        long collectionId = (Long)jsonObject.get("id");
        long durationInHours = (Long)jsonObject.get("durationInHours");
        Boolean updateDuration = (Boolean)jsonObject.get("updateDuration");

        if(updateDuration){
            String token = (String)jsonObject.get("token");
            if(!collectionService.isValidToken(token)){
                logger.error("authentication failed : token - " + token);
                return getUIWrapper(false);
            }
        }

        try{
            //logger.info("try:" + geoString) ;
            Collection dbCollection = collectionService.findById(collectionId);
            if(dbCollection != null) {
	            CollectionStatus status = dbCollection.getStatus();
	
	            if (CollectionStatus.RUNNING_WARNING.equals(status) || CollectionStatus.RUNNING.equals(status) || CollectionStatus.INITIALIZING.equals(status)) {
	            	collectionService.stop(dbCollection.getId(), 1L);
	            }
	
	            dbCollection.setGeo(geoString);
	            dbCollection.setGeoR(GeoRestrictionType.STRICT.name().toLowerCase());
	            dbCollection.setDurationHours((int)durationInHours);
	            collectionService.update(dbCollection);
	
	            if(!geoString.isEmpty() && geoString != null) {
	                // status
	                collectionService.startFetcher(collectionService.prepareFetcherRequest(dbCollection), dbCollection);
	            }
	            return getUIWrapper(true);
            }

        }catch(Exception e){
            logger.error(String.format("Exception while Updating Collection : "+jsonCollection, e));
        }
        
        return getUIWrapper(false);
    }

    @RequestMapping(value = "/findByRequestCode.action", method = RequestMethod.GET)
    @ResponseBody
    public Map<String,Object>  findByRequestCode(@QueryParam("code") String code) throws Exception {
        try {
        	//logger.info("Finding collection by code: "+code);
            Collection data = collectionService.findByCode(code);
            return getUIWrapper(data, true);

        } catch (Exception e) {
        	logger.error("Exception while finding collection by code: "+code, e);
            return getUIWrapper(false);
        }

    }

    @RequestMapping(value = "/findAll.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object>  findAll(@RequestParam Integer start, @RequestParam Integer limit,  @RequestParam Enum statusValue,
			@DefaultValue("no") @QueryParam("trashed") String trashed) throws Exception {
		start = (start != null) ? start : 0;
		limit = (limit != null) ? limit : 50;

		try {

			List<Collection> data = collectionService.findAllForPublic(start, limit, statusValue);
			logger.info("[findAll] fetched data size: " + ((data != null) ? data.size() : 0));
			return getUIWrapper(data, true);

		} catch (Exception e) {
			logger.error("Error in find All collection for public",e);
			return getUIWrapper(false);
		}

		//return getUIWrapper(false);
	}


	@RequestMapping(value = "/create", method={RequestMethod.POST})
	@ResponseBody
	public Map<String,Object> createCollection(CollectionDetailsInfo collectionDetailsInfo,
			@RequestParam(value = "runAfterCreate", defaultValue = "false", required = false)
			Boolean runAfterCreate) throws Exception {

		
		logger.info("Save Collection to Database having code : "+ collectionDetailsInfo.getCode());
		
		
		//logger.info("Following users: " + collection.getFollow());
		try{
			UserAccount user = getAuthenticatedUser();
			Collection collection = collectionService.create(collectionDetailsInfo, user);
			
			return getUIWrapper(true);  
		}catch(Exception e){
			logger.error("Error while saving Collection Info to database", e);
			return getUIWrapper(false); 
		}
	}
	

	@RequestMapping(value = "/findAllRunning.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object>  findAllRunning(@RequestParam Integer start, @RequestParam Integer limit,
			@DefaultValue("no") @QueryParam("trashed") String trashed) throws Exception {
		start = (start != null) ? start : 0;
		limit = (limit != null) ? limit : 50;
		Integer count = 0;
		List<AidrCollectionTotalDTO> dtoList = new ArrayList<AidrCollectionTotalDTO>();

		try {
			//logger.info("*************************************************  CollectionStatus.RUNNING ****************************");
			List<Collection> data = collectionService.findAllForPublic(start, limit, CollectionStatus.RUNNING);
			//logger.info("data size : " + data.size());

			for (Collection collection : data) {
				String taggingOutPut = taggerService.loadLatestTweetsWithCount(collection.getCode(), 1);
				//String stripped = taggingOutPut.substring(1, taggingOutPut.lastIndexOf("]"));
				//logger.info("stripped taggingOutPut : " + taggingOutPut );
				if(!JsonDataValidator.isEmptySON(taggingOutPut))  {
					AidrCollectionTotalDTO dto = convertAidrCollectionToDTO(collection, true);
					dtoList.add(dto);
					count = count +1;
				}
			}
			//logger.info("count = " + count);
			return getUIWrapper(dtoList, count.longValue());

		} catch (Exception e) {
			logger.error("Error in find All Running collection for public",e);
			return getUIWrapper(false);
		}

	}

	@RequestMapping(value = "/findAllRunningWithNoOutput.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object>  findAllRunningWithNoOutput(@RequestParam Integer start, @RequestParam Integer limit,
			@DefaultValue("no") @QueryParam("trashed") String trashed) throws Exception {
		start = (start != null) ? start : 0;
		limit = (limit != null) ? limit : 50;
		Integer count = 0;
		List<AidrCollectionTotalDTO> dtoList = new ArrayList<AidrCollectionTotalDTO>();

		try {
			List<Collection> data = collectionService.findAllForPublic(start, limit, CollectionStatus.RUNNING);
			//count = collectionService.getPublicCollectionsCount(CollectionStatus.RUNNING);
			for (Collection collection : data) {
				String taggingOutPut = taggerService.loadLatestTweetsWithCount(collection.getCode(), 1);
				if(JsonDataValidator.isEmptySON(taggingOutPut))  {
					AidrCollectionTotalDTO dto = convertAidrCollectionToDTO(collection, false);
					dtoList.add(dto);
					count = count +1;
				}
			}
			//logger.info("count = " + count);
			return getUIWrapper(dtoList, count.longValue());

		} catch (Exception e) {
			logger.error("Error in find All Running collection With No Output for public",e);
			return getUIWrapper(false);
		}

		//return getUIWrapper(false);
	}

	@RequestMapping(value = "/findAllStoped.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object>  findAllStop(@RequestParam Integer start, @RequestParam Integer limit,
			@DefaultValue("no") @QueryParam("trashed") String trashed) throws Exception {
		start = (start != null) ? start : 0;
		limit = (limit != null) ? limit : 50;
		Integer count = 0;
		List<AidrCollectionTotalDTO> dtoList = new ArrayList<AidrCollectionTotalDTO>();
		try {
			List<Collection> data = collectionService.findAllForPublic(start, limit, CollectionStatus.STOPPED);
			count = collectionService.getPublicCollectionsCount(CollectionStatus.STOPPED);
			boolean hasTagggerOutput;
			for (Collection collection : data) {
				String taggingOutPut = taggerService.loadLatestTweetsWithCount(collection.getCode(), 1);
				if(JsonDataValidator.isEmptySON(taggingOutPut))  {
					hasTagggerOutput = false;
				}
				else{
					hasTagggerOutput = true;
				}

				AidrCollectionTotalDTO dto = convertAidrCollectionToDTO(collection, hasTagggerOutput);
				dtoList.add(dto);
			}
			//logger.info("count = " + count);
			return getUIWrapper(dtoList, count.longValue());

		} catch (Exception e) {
			logger.error("Error in finding All stopped collections",e);
			return getUIWrapper(false);
		}
	}

	@RequestMapping(value = "/findById.action", method = RequestMethod.GET)
	@ResponseBody
	public AidrCollectionTotalDTO findById(Long id) throws Exception {

		Collection collection = collectionService.findById(id);
		AidrCollectionTotalDTO dto = convertAidrCollectionToDTO(collection, false);
		if (dto != null) {
			Integer totalCount = collectionLogService.countTotalDownloadedItemsForCollection(id);
			if (CollectionStatus.RUNNING.equals(dto.getStatus()) || CollectionStatus.RUNNING_WARNING.equals(dto.getStatus())){
				totalCount += dto.getCount();
			}
			dto.setTotalCount(totalCount);
		}
		return dto;
	}


	@RequestMapping(value = "/generateTweetIdsLink.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object> generateTweetIdsLink(@RequestParam String code) throws Exception {
		Map<String, Object> result = null;
		try {
			result = collectionLogService.generateTweetIdsLink(code);
			if (result != null && result.get("url") != null) {
				return getUIWrapper(result.get("url"),true, null, (String)result.get("message"));
			} else {
				return getUIWrapper(false, "System is down or under maintenance. For further inquiries please contact admin.");
			}
		} catch (Exception e) {
			logger.error("Error in generateTweetIdsLink for collection : " + code, e);
			return getUIWrapper(false, "System is down or under maintenance. For further inquiries please contact admin.");
		}
	}

	@RequestMapping(value = "/getAttributesAndLabelsByCrisisId.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object> getAttributesAndLabelsByCrisisId(@RequestParam Integer id) throws Exception {
		String result = "";
		try {
			result = taggerService.getAttributesAndLabelsByCrisisId(id);
		} catch (Exception e) {
			logger.error("Error while getting attributes and labels for crisis: " + id, e);
			return getUIWrapper(false, "System is down or under maintenance. For further inquiries please contact admin.");
		}
		return getUIWrapper(result,true);
	}


	@RequestMapping(value = "/loadLatestTweets.action", method = RequestMethod.GET)
	@ResponseBody
	public Map<String,Object> loadLatestTweets(@RequestParam String code, @RequestParam String constraints) throws Exception {
		String result = "";
		try {
			result = taggerService.loadLatestTweets(code, constraints);
		} catch (Exception e) {
			logger.error("Error while loading latest tweets for collection : " + code + " and constraints : " + constraints, e);
			return getUIWrapper(false, "System is down or under maintenance. For further inquiries please contact admin.");
		}
		return getUIWrapper(result,true);
	}


	@RequestMapping(value = "/getPublicFlagStatus", method = RequestMethod.GET)
	@ResponseBody
	public Map<String, Boolean> getPublicFlagStatus() {
		List<Collection> resultList;
		try {
			//long startTime = System.currentTimeMillis();
			resultList = collectionService.getRunningCollections();
			if (resultList != null) {
				Map<String, Boolean> runningCollections = new HashMap<String, Boolean>(resultList.size());
				for (Collection c: resultList) {
					runningCollections.put(c.getCode(), c.isPubliclyListed());
				}
				//logger.debug("Fetched map to send: " + runningCollections);
				return runningCollections;
			}
		} catch (Exception e) {
			logger.error("Unable to fetch list of running collections from DB for public", e);
		}
		return null;
	}


	@RequestMapping(value = "/getChannelPublicFlagStatus", method = RequestMethod.GET)
	@ResponseBody
	public Map<String, Boolean> getCollectionPublicFlagStatus(@QueryParam("channelCode") String channelCode) {
		Collection collection = null;
		try {
			//long startTime = System.currentTimeMillis();
			collection = collectionService.findByCode(channelCode);
			//System.out.println("Time to retrieve publiclyStatus from DB: " + (System.currentTimeMillis() - startTime));

			if (collection != null) {
				Map<String, Boolean> result = new HashMap<String, Boolean>();
				result.put(channelCode, collection.isPubliclyListed());
				//logger.debug("Fetched map to send: " + result);
				return result;
			}
		} catch (Exception e) {
			logger.error("Unable to fetch running status for collection: "+channelCode,e);
		}
		return null;
	}


	@RequestMapping(value = "/findTotalCount", method = RequestMethod.GET)
	@ResponseBody
	public Map<String, Integer> findTotalCount(final String collectionCode) throws Exception {
		try {
			Collection collection = collectionService.findByCode(collectionCode);
			AidrCollectionTotalDTO dto = convertAidrCollectionToDTO(collection, false);
			if (dto != null) {
				Integer totalCount = collectionLogService.countTotalDownloadedItemsForCollection(dto.getId());
				if (CollectionStatus.RUNNING.equals(dto.getStatus()) || CollectionStatus.RUNNING_WARNING.equals(dto.getStatus())){
					totalCount += dto.getCount();
				}
				dto.setTotalCount(totalCount);
			}
			Map<String, Integer> result = new HashMap<String, Integer>();
			result.put(collectionCode, dto.getTotalCount());
			return result;
		} catch (Exception e) {
			logger.error("Unable to fetch total count of downloaded documents for collection = " + collectionCode, e);
		}
		return null;
	}

	
	private AidrCollectionTotalDTO convertAidrCollectionToDTO(Collection collection, boolean hasTaggerOutput){
		if (collection == null){
			return null;
		}

		AidrCollectionTotalDTO dto = new AidrCollectionTotalDTO();

		dto.setId(collection.getId());
		dto.setCode(collection.getCode());
		dto.setName(collection.getName());
		//dto.setTarget(collection.getTarget());

		UserAccount user = collection.getOwner();
		dto.setUser(user);

		if (collection.getCount() != null) {
			dto.setCount(collection.getCount());
		} else {
			dto.setCount(0);
		}
		dto.setStatus(collection.getStatus());
		dto.setTrack(collection.getTrack());
		dto.setFollow(collection.getFollow());
		dto.setGeo(collection.getGeo());
		dto.setLangFilters(collection.getLangFilters());
		dto.setStartDate(collection.getStartDate());
		dto.setEndDate(collection.getEndDate());
		dto.setCreatedDate(collection.getCreatedAt());
		dto.setLastDocument(collection.getLastDocument());
		dto.setDurationHours(collection.getDurationHours());
		dto.setPubliclyListed(collection.isPubliclyListed());
		dto.setCrisisType(collection.getCrisisType());
		dto.setHasTaggerOutput(hasTaggerOutput);
        dto.setCollectionType(collection.getProvider());
		dto.setCrisisType(collection.getCrisisType());
		
		List<UserAccount> managers = collaboratorService.fetchCollaboratorsByCollection(collection.getId());
		dto.setManagers(managers);

		return dto;
	}

	private String getCrisisTypeName(int typeID){
		String name = "Not specified";
		try {
			List<TaggerCrisisType> crisisTypes = taggerService.getAllCrisisTypes();

			for (TaggerCrisisType cType : crisisTypes) {
				if(cType.getCrisisTypeID() == typeID) {
					name = cType.getName();
				}
			}

		} catch (AidrException e) {
			logger.error("Error while fetching all crisisTypes for public",e);
		}

		return name;
	}

	@RequestMapping(value = "/stopAllRunningCollection", method = RequestMethod.POST)
	@ResponseBody
	public Map<String,Object> stopAllRunningCollections(@RequestBody final String jsonString) throws Exception {
		if(jsonString == null){
			return getUIWrapper(false);
		}
		
		JSONParser parser = new JSONParser();
		JSONObject jsonObject = (JSONObject)parser.parse(jsonString);
		
		String token = (String)jsonObject.get("token");
		
		if(collectionService.isValidToken(token)){
			List<Collection> runningCollections = collectionService.getRunningCollections();
			
			List<Collection> stoppedCollections = new ArrayList<Collection>();
			
			for (Collection aidrCollection : runningCollections) {
				stoppedCollections.add(collectionService.stop(aidrCollection.getId(), 1L));
			}
			return getUIWrapper(stoppedCollections, true);
		}
		else{
			return getUIWrapper("Authentication Failed", false);
		}
	}

	
	@RequestMapping(value = "/startCollections", method = RequestMethod.POST)
	@ResponseBody
	public Map<String,Object> startMultipleCollections(@RequestBody String jsonString) throws Exception {
		if(jsonString == null){
			return getUIWrapper(false);
		}
		
		JSONParser parser = new JSONParser();
		JSONObject jsonObject = (JSONObject)parser.parse(jsonString);
		
		String token = (String)jsonObject.get("token");
		List<Collection> collections = new ArrayList<Collection>();
		
		if(collectionService.isValidToken(token)){
			ObjectMapper mapper = new ObjectMapper();
			collections = Arrays.asList(mapper.readValue(jsonObject.get("collections").toString(), Collection[].class));
			List<String> startedCollections = new ArrayList<String>();

			for (Collection collection : collections) {
				collection = collectionService.findByCode(collection.getCode());
				if (!collection.getStatus().equals(CollectionStatus.TRASHED)) {
					collection = collectionService.start(collection.getId());
					startedCollections.add(collection.getCode());
				}
			} 
			return getUIWrapper(startedCollections, true);
		}
		else{
			return getUIWrapper("Authentication Failed", false);
		}
	}
	
	@RequestMapping(value = "/sms/push/{collectionCode}", method = RequestMethod.POST)
	@ResponseBody
    public Map<String,Object> receive(@PathVariable(value = "collectionCode") String collectionCode, @RequestHeader("apiKey") String apiKey, @RequestBody SMS sms) throws Exception {
		
		if(collectionService.isValidAPIKey(collectionCode, apiKey)){
			Boolean response = collectionService.pushSMS(collectionCode, sms);
			if(response){
				return getUIWrapper("Successfully posted SMS", true);
						//Response.ok("Successfully posted SMS").build();
			}
			else{
				return getUIWrapper("Exception while posting SMS", false); //Response.ok("Exception while posting SMS").build();
			}
		}
		else{
			return getUIWrapper("API Key is not valid", false); //Response.ok("API Key is not valid").build();
		}
	}
	
	@RequestMapping(value = "{usage}/all")
	@ResponseBody
	public List<CollectionSummaryInfo> getCollections(@PathVariable(value = "usage") String usage) {
		
		List<CollectionSummaryInfo> summaryInfos = new ArrayList<CollectionSummaryInfo>();
		try {
			UsageType usageType = UsageType.valueOf(usage);
			summaryInfos =  collectionService.getAllCollectionDataByUsage(usageType);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			logger.error("error", e);
			return Collections.EMPTY_LIST;
		}
		
		return summaryInfos;
	}
	
	@RequestMapping(value = "/list")
	@ResponseBody
	public List<CollectionBriefInfo> getCollectionsBriefInfo(@RequestParam(defaultValue="false") Boolean micromappersEnabled) {
		List<CollectionBriefInfo> briefInfos = new ArrayList<CollectionBriefInfo>();
		try {
			briefInfos = collectionService.getMicromappersFilteredCollections(micromappersEnabled);
		} catch (Exception e) {
			logger.error("Error is fetching collection list.", e);
		}
		
		return briefInfos;
	}
	
	@RequestMapping("/statistics")
	@ResponseBody
	public String getCollectionStatistics() throws Exception {
		Map<String, Object> result = new HashMap<>();
		ObjectMapper mapper = new ObjectMapper();
		try {
		    result = getUIWrapper(collectionService.getCollectionStatistics(),true);
		} catch (Exception e) {
			logger.error("Error while fetching tweets counts", e);
		    result = getUIWrapper(false, "System is down or under maintenance. For further inquiries please contact admin.");
		}
		return "jsonp(" + mapper.writeValueAsString(result) + ")";
	}
	
}