package com.bayer.edam.migration;

import java.io.File;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import com.bayer.edam.migration.adam.Campaign;
import com.bayer.edam.migration.adam.EntityId;
import com.bayer.edam.migration.adam.Token;
import com.bayer.edam.migration.adam.WriteRecord;
import com.bayer.edam.migration.adam.WriteRecord.Classification;
import com.bayer.edam.migration.mam.Entry;
import com.bayer.edam.migration.mam.categories.CategoryTree;
import com.bayer.edam.migration.mam.categories.CategoryTree.Node;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.univocity.parsers.csv.CsvParser;
import com.univocity.parsers.csv.CsvParserSettings;
import com.univocity.parsers.csv.CsvWriter;
import com.univocity.parsers.csv.CsvWriterSettings;

@SpringBootApplication
@PropertySource("file:secret.properties")
public class MamMigrationSelective implements CommandLineRunner {

	private static final Logger log = LoggerFactory.getLogger(MamMigrationSelective.class);
	
	static final String adamHost = "https://edam-q.bayer.com:2015";
	static final String mamHost = "https://service.media-assistant.animalhealth.bayer.com";
	static final int[] selection = { 71948, /*71048, 70778, 70746, 70142,*/ 70361, /*63924, 71263, 63201, 56942, 57641, 70745, 70440, 70340,
			70288, 71725*/ };
	
	static final String categoryJson = "C:\\Users\\gexce\\git\\mam-migration\\AH MAM Migration\\resources\\2017-10-19_categories.json";
	static final String categoryMapping = "C:\\Users\\gexce\\git\\mam-migration\\AH MAM Migration\\resources\\Media_Assistant_Categories_131017_EDITED.csv";
	
	static final File entryLog = new File("C:\\Temp\\Migration AH\\files\\entry.log");
	static final File problemLog = new File("C:\\Temp\\Migration AH\\files\\problem.log");
	
	static final String fieldSeparator = "|~|";
	static final String lineSeparator = "\r\n";

	@Value("${mam.auth}")
	private String mamAuth;

	@Value("${adam.auth}")
	private String adamAuth;

	RestTemplate restTemplate;
	HttpHeaders adamHeaders, mamHeaders;
	HttpEntity<String> adamHeadersEntity;

	private SimpleDateFormat isoDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
	private SimpleDateFormat stringDate = new SimpleDateFormat("yyyy-MM-dd");
	private ObjectMapper jsonMapper = new ObjectMapper();

	private List<String[]> campaigns;
	private List<String> locations = new ArrayList<String>();
	private CategoryTree mamCategoryTree;
	
	@Override
	public void run(String... strings) throws Exception {
		
		log.info("Write log headers");
		FileUtils.writeStringToFile(entryLog, "Location" + fieldSeparator + "Entry" + lineSeparator, Charset.forName("utf-8"));
		FileUtils.writeStringToFile(problemLog, "entryId" + fieldSeparator + "Entry" + lineSeparator, Charset.forName("utf-8"));
		
		log.info("Read categories");
		mamCategoryTree = new CategoryTree(categoryJson, categoryMapping);
		
		log.info("Read campaigns");
		// TODO HIER GEHT'S WEITER - die vorhandenen Campaigns per REST aufrufen und in campaigns speichern

		restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
		List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
		interceptors.add(new LoggingRequestInterceptor());
		restTemplate.setInterceptors(interceptors);

		adamHeaders = createAdamHeaders();
		adamHeadersEntity = new HttpEntity<String>("", adamHeaders);
		mamHeaders = createMamHeaders();

		HttpEntity<String> entity = new HttpEntity<String>("", mamHeaders);
		for (int id : selection) {
			log.info("GET entry " + id);

			Entry entry = restTemplate.exchange(mamHost + "/rest/migration/entry/{id}", HttpMethod.GET, entity, Entry.class, id).getBody();
			
			// check exclusion criteria
			if (entry.stateId == 7) {
				log.info("Skipping entry " + entry.id + ": Status " + entry.stateId + "(LOCKED_BY_ADMIN)");
				continue;
			}
			else if (entry.stateId == 8) {
				log.info("Skipping entry " + entry.id + ": Status " + entry.stateId + "(ASSIGNED_TO_INDEXATION)");
				continue;
			}
			else if (entry.stateId == 10) {
				log.info("Skipping entry " + entry.id + ": Status " + entry.stateId + "(ARCHIVED)");
				continue;
			}
			
			postToAdam(entry);
		}
		
//		// delete all created records
//		for (String location : locations) {
//			ResponseEntity<String> response = restTemplate.exchange(adamHost + location, HttpMethod.DELETE, adamHeadersEntity,
//					String.class);
//			log.info("Deleted " + location + ": " + response.toString());
//		}

		invalidateAdamToken();
	}

	void postToAdam(Entry entry) throws Exception {
		WriteRecord record = new WriteRecord();
		String entryJson = jsonMapper.writeValueAsString(entry);

		// globally applicable classifications
		record.addClassification("MediaAssistant-Migration");
		record.addClassification("Country.Global");
		record.addClassification("Agencies.Bayer AH");
		record.addClassification("Usage.Restricted");
		
		// media type
		record.addClassification(Definitions.mediaTypes.get(entry.mediaType.id));
		
		// process categories
		Set<String> keywords = new HashSet<String>();
		
		if (entry.categories.size() > 1)
			log.warn("Entry " + entry.id + " has " + entry.categories.size() + " categories assigned");
		
		for (int categoryId : entry.categories) {
			log.debug("Category: " + categoryId);
			
			Node node = mamCategoryTree.getNode(categoryId);
			List<Node> path = new LinkedList<Node>();
			
			Node tmpNode = node;
			while (tmpNode.id != CategoryTree.ROOT_NODE_ID) {
				path.add(0, tmpNode);
				tmpNode = tmpNode.parent;
			}
			
			for (Node n : path) {
				if (n.action.equals("not migrated")) {
					log.warn("Skipping entry " + entry.id + " because of tag on (parent) category " + n.name + " (" + n.id + ")");
					return;
				}
				
				if (n.action.equals("classify")) {
					record.addClassification(n.parameter);
				}
				else if (n.action.equals("brand")) {
					String brandClassificationId = "Brand." + n.name.trim();
					record.addClassification(brandClassificationId);
				}
				else if (n.action.equals("campaign")) {
					String campaignName = n.name;
					String campaignLabel = n.name;
					String campaignYear;
					if (n.parameter != null && !n.parameter.trim().equals("")) {
						campaignLabel = campaignLabel + " " + n.parameter;
						campaignYear = n.parameter.substring(1, 5);
					} else {
						campaignYear = n.name.substring(0, 4);
					}

					try {
						Integer.parseInt(campaignYear);
						
						// check whether campain already exists
						String campaignGuid = null;
						for (String[] line : campaigns) {
							if (line[0].equals(campaignName)) {
								campaignGuid = line[2];
								log.debug("Found already existing campaign '" + campaignName + "' with guid " + campaignGuid);
							}
						}
						
						if (campaignGuid == null) {
							log.info("Create campaign " + campaignName);
							
							Campaign campaign = new Campaign(campaignName, campaignLabel, campaignYear);
							
							HttpEntity<Campaign> httpEntity = new HttpEntity<Campaign>(campaign, adamHeaders);
							try {
								EntityId campaignId = restTemplate.exchange(adamHost + "/classifications", HttpMethod.POST, httpEntity, EntityId.class).getBody();
								campaignGuid = campaignId.id;
								
								log.info("Created campaign: " + campaignName + ";" + campaignLabel + ";" + campaignGuid);
								record.addClassification(new Classification(campaignGuid, 1));
								
								// TODO better write campaign to file right away?
								campaigns.add(new String[] { campaignName, campaignLabel, campaignGuid });

							} catch (HttpStatusCodeException hsce) {
								log.error("Could not create campaign '" + campaignName + "' for entry " + entry.id + ": " + hsce.getStatusCode().toString());
								log.error(hsce.getResponseBodyAsString());
							}
						}
					} catch (NumberFormatException nfe) {
						log.warn("Could not parse year from string " + campaignYear);
					}
				}
				else if (n.action.equals("keyword")) {
					log.debug("Adding keyword " + n.name.trim());
					keywords.add(n.name.trim());
				}
			}
			
		}
		
		// Fields
		record.addField("byr_Title", entry.name.get("en"));
		
		// TODO
		record.addField("byr_ah_projectLead", Collections.singletonList(Definitions.defaultProjectLead));
		record.addField("byr_ah_brandCommunicationRepresentative", Collections.singletonList(Definitions.defaultProjectLead));

		// Description / Additional Information
		String description = entry.description.get("en");
		if (description == null)
			description = "";
		else
			description += "\n\n";

		if (entry.comment.get("en") != null)
			description += "Comment: " + entry.comment.get("en") + "\n\n";

		String migrationDescription = "Migrated from Media Assistant ID " + entry.id + "\nDate of Issue: "
				+ stringDate.format(entry.creationDate);
		description += migrationDescription;
		record.addField("byr_ah_description", description);

		// Keywords
		if (entry.keyword.get("en") != null && entry.keyword.get("en").length() > 0) {
			keywords.addAll(Arrays.asList(entry.keyword.get("en").split("\\s*,\\s*")));
		}
		
		if (keywords.size() > 0) {
			record.addField("byr_ah_keywords_tl", new ArrayList<String>(keywords));
		}
		
		// License Information
		record.addField("byr_ah_licenseHolderList", Collections.singletonList(Definitions.defaultLicenseHolder));
		
		// if license holder is not "Bayer Animal Health GmbH" put on review list
		if (!entry.copyright.organization.get("id").equals("3740")) {
			log.warn("Entry " + entry.id + ": License Holder is not set to 'Bayer Animal Health GmbH' (organization 3740)");
		}
		
		record.addField("byr_ah_licenseAdditionalPermittedUse", Collections.singletonList(Definitions.defaultAdditionalPermittedUse));
		
		String licenseDescription = "";
		if (entry.copyright.use != null)
			licenseDescription +=  entry.copyright.use;
		record.addField("byr_ah_licenseComments", licenseDescription);
		
//		if (entry.gpc != null)
//			record.addField("byr_ah_approvalNumber",entry.gpc.gpcNumber);

		
		// Create record
		HttpEntity<WriteRecord> httpEntity = new HttpEntity<WriteRecord>(record, adamHeaders);
		try {
			URI location = restTemplate.postForLocation(adamHost + "/records", httpEntity, WriteRecord.class);
			log.info("Created " + location);

			FileUtils.writeStringToFile(entryLog, location.toString() + fieldSeparator + entryJson + lineSeparator,
					Charset.forName("utf-8"), true);

			locations.add(location.toString());

		} catch (HttpStatusCodeException hsce) {
			log.error("Skipping entry " + entry.id + ": Error while creating record " + hsce.getStatusCode().toString());
			log.error(hsce.getResponseBodyAsString());
			FileUtils.writeStringToFile(problemLog, entry.id + fieldSeparator + entryJson + lineSeparator, Charset.forName("utf-8"), true);
		}
	}
	
	/**
	 * Checks whether the given campaign already exists and creates it, if not.
	 * 
	 * @return the guid of the campaign
	 */
	private String getCampaign(int year, String label, String name) {
		return null;
	}

	private HttpHeaders createMamHeaders() {
		HttpHeaders headers = new HttpHeaders();
		headers.add("Accept", "application/json");
		headers.add("Authorization", "Basic " + mamAuth);
		headers.add("Content-Type", "application/json");
		headers.add("From", "migration@bayer.com");

		return headers;
	}

	private HttpHeaders createAdamHeaders() {
		HttpHeaders headers = new HttpHeaders();
		headers.add("Registration", "ADAMBAYER");
		headers.add("API-VERSION", "1");
		headers.add("Accept", "application/hal+json");
		headers.add("Content-Type", "application/json");
		headers.add("Authorization", "Basic " + adamAuth);
		headers.add("set-immediateSearchIndexUpdate", "true"); // see https://edam.bayer.com:2015/docs/resources/record#example-records-direct-push
		HttpEntity<String> entity = new HttpEntity<String>("", headers);
		Token token = restTemplate.exchange(adamHost + "/auth", HttpMethod.GET, entity, Token.class).getBody();

		// replace auth header
		headers.remove("Authorization");
		headers.add("Authorization", "Token " + token.getToken());

		// replace accept-charset header (in order to declutter log)
		headers.add("Accept-Charset", "us-ascii, utf-16, utf-8");

		return headers;
	}

	private void invalidateAdamToken() {
		ResponseEntity<String> response = restTemplate.exchange(adamHost + "/auth", HttpMethod.DELETE, adamHeadersEntity, String.class);
		log.info("Logged out from adam API: " + response.toString());
	}
	
	/*
	 * // Quoted "Z" to indicate UTC, no timezone offset private static SimpleDateFormat outputDateFormat = new
	 * SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); private static SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy-MM-dd");
	 * 
	 * public static String toIso8601(String isoDate) { try { return outputDateFormat.format(inputDateFormat.parse(isoDate)); } catch
	 * (ParseException e) { log.error(e.toString()); e.printStackTrace(); } return null; }
	 */

	public static void main(String args[]) {
		SpringApplication.run(MamMigrationSelective.class, args);
	}

}
