package com.alexanderlogistics;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ConversationScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.faces.data.WorkflowController;
import org.imixs.workflow.office.views.SearchController;

/**
 * Der InvoiceAnalyseController dient zur Auswertung der Sachprüfungs WOrklfow
 * Schritte. Die Analyse Seite ist büer das Admin Menü erreichbar.
 * 
 * @author rsoika
 *
 */
@Named
@ConversationScoped
// @SessionScoped
public class DebitorStatistikController implements Serializable {

	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(DebitorStatistikController.class.getName());

	@Inject
	protected DocumentService documentService;

	TreeMap<String, DebitorStatistikData> stats = null;

	@Inject
	protected WorkflowController workflowController;

	@Inject
	protected SearchController searchController;

	@Inject
	protected KreditorDebitorService kreditorDebitorService;

	/**
	 * Initialize default behavior initialize an opitonal id from the query string
	 * 
	 */
	@PostConstruct
	public void init() {
		// extract the processref and page from the query string
		FacesContext fc = FacesContext.getCurrentInstance();
		Map<String, String> paramMap = fc.getExternalContext().getRequestParameterMap();

		String id = paramMap.get("dbtr.number");
		if (id != null && !id.isEmpty()) {
			workflowController.getWorkitem().setItemValue("dbtr.number", id);
			try {
				ItemCollection dbtr = kreditorDebitorService.findDebitor(id);
				if (dbtr != null) {
					workflowController.getWorkitem().setItemValue("dbtr.name", dbtr.getItemValueString("_vendor_name"));
				}
			} catch (PluginException e) {
				// e.printStackTrace();
			}
		}
	}
	public ItemCollection getWorkitem() {
		return workflowController.getWorkitem();
	}

	public int getZahlungsmoral() {
		return 0;
	}

	/**
	 * Gibt eine bookmarkable url zurück.
	 * Wir vom Aktualisieren button in der Page genutzt
	 * 
	 * @return
	 */
	public String getBookmark() {
		return "/pages/dbtr-stats.xhtml?dbtr.number=" + getDbtNr() + "&faces-redirect=true";
	}
	/*
	 * Gesamt
	 */
	public String getQueryRechnungen() {
		String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"";
		return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " gesamt&query=" + query;
	}
	public int getRechnungen() {
		int result = 0;
		if (getDbtNr().isEmpty()) {
			return 0;
		}
		try {
			result = documentService
					.count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr()
							+ " AND $workflowgroup:\"Rechnungsausgang\"");
		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		return result;
	}


	/*
	 * Bezahlt
	 */
	public String getQueryRechnungenBezahlt() {
		String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5900 TO 5999])";
		return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " bezahlt&query=" + query;
	}
	public int getRechnungenBezahlt() {
		int result = 0;
		if (getDbtNr().isEmpty()) {
			return 0;
		}
		try {
			result = documentService
					.count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr()
							+ " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])");
		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		return result;
	}

	/*
	 * Offen
	 */
	public String getQueryRechnungenOffen() {
		String query = "(type:workitem) AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5000 TO 5099])";
		return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " offen&query=" + query;
	}
	public int getRechnungenOffen() {
		int result = 0;
		if (getDbtNr().isEmpty()) {
			return 0;
		}
		try {
			result = documentService
					.count("(type:workitem) AND dbtr.number:" + getDbtNr()
							+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5000 TO 5099])");
		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		return result;
	}

	/*
	 * Fällig
	 */
	public String getQueryRechnungenFaellig() {
		String query = "(type:workitem) AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5100 TO 5199])";
		return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " fällig&query=" + query;
	}
	public int getRechnungenFaellig() {
		int result = 0;
		if (getDbtNr().isEmpty()) {
			return 0;
		}
		try {
			result = documentService
					.count("(type:workitem) AND dbtr.number:" + getDbtNr()
							+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5100 TO 5199])");
		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		return result;
	}

	/*
	 * In Mahnung
	 */
	public String getQueryRechnungenGemahnt() {
		String query = "(type:workitem) AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5200 TO 5299])";
		return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Mahnung&query=" + query;
	}
	public int getRechnungenGemahnt() {
		int result = 0;
		if (getDbtNr().isEmpty()) {
			return 0;
		}
		try {
			result = documentService
					.count("(type:workitem ) AND dbtr.number:" + getDbtNr()
							+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5200 TO 5299])");
		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		return result;
	}

	/*
	 * Inkasso
	 */
	public String getQueryRechnungenInkasso() {
		String query = "(type:workitem) AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5300 TO 5399])";
		return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Inkasso&query=" + query;
	}
	public int getRechnungenInkasso() {
		int result = 0;
		if (getDbtNr().isEmpty()) {
			return 0;
		}
		try {
			result = documentService
					.count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr()
							+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5300 TO 5399])");
		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		return result;
	}

	/**
	 * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt
	 * 
	 * @return
	 */
	public String getDbtNr() {
		String dbtNr = getWorkitem().getItemValueString("dbtr.number");
		if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) {
			dbtNr = dbtNr.substring(1);
		}
		return dbtNr;
	}

	public long getDurchschnittZahlungsziel() {
		getStats();
		int count = 0;
		long gesamt = 0;
		for (Map.Entry<String, DebitorStatistikData> entry : stats.entrySet()) {
			DebitorStatistikData statData = entry.getValue();
			if (statData.count > 0) {
				gesamt = gesamt + statData.getAverageDueDays();
				count++;
			}
		}
		return Math.round((double) gesamt / count);
	}

	public long getDurchschnittZahlungsdauer() {
		getStats();
		int count = 0;
		long gesamt = 0;
		for (Map.Entry<String, DebitorStatistikData> entry : stats.entrySet()) {
			DebitorStatistikData statData = entry.getValue();
			if (statData.count > 0) {
				gesamt = gesamt + statData.getAveragePaymentDays();
				count++;
			}
		}
		return Math.round((double) gesamt / count);
	}

	/**
	 * Sucht alle Rechnugnen aus einem Zeitraum und sammelt Zahlungsziel und
	 * Zahlungszeitspanne gruppiert nach monaten
	 * 
	 */
	public Map<String, DebitorStatistikData> getStats() {

		if (stats != null) {
			return stats;
		}

		// recompute
		stats = new TreeMap<String, DebitorStatistikData>();
		if (getDbtNr().isEmpty()) {
			return stats;
		}
		// invoice.date:[20210101 TO 20210201]
		DateFormat df = new SimpleDateFormat("yyyymmdd");

		String query = "(type:workitemarchive) AND invoice.date:[20221201 TO 20500101] AND dbtr.number:" + getDbtNr()
				+ " AND $workflowgroup:\"Rechnungsausgang\"  AND ($taskid:[5900 TO 5999])";

		// query = query + " AND (invoice.date:[" + df.format(from) + " TO " +
		// df.format(to) + "])";
		logger.fine("query = " + query);
		try {
			List<ItemCollection> invoices = documentService
					.findStubs(query, 9999, 0, "$created", false);

			for (ItemCollection invoice : invoices) {
				try {

					logger.fine("Rechnung: " + invoice.getUniqueID());
					Date invoiceDate = invoice.getItemValueDate("invoice.date");
					Date invoiceDueDate = invoice.getItemValueDate("invoice.duedate");
					Date paymentDate = findPaymentDateByWorkitem(invoice);
					if (paymentDate == null) {
						logger.warning("Es wurde kein Zahlungseingang gefunden");
						continue;
					}

					if (invoiceDate != null && paymentDate != null && invoiceDueDate != null) {
						// do we have a stats object?
						DebitorStatistikData statData = stats.get(getYearMonth(invoiceDate));
						if (statData == null) {
							// create a new one
							statData = new DebitorStatistikData(invoiceDate);
						}
						// jetzt irgendwas ausrechnen
						statData.compute(invoiceDate, invoiceDueDate, paymentDate);
						// .. und wieder speichern
						stats.put(statData.toString(), statData);

					} else {
						logger.warning("No payment Date found for invoice " + invoice.getUniqueID());
					}

				} catch (ParseException e) {
					logger.warning("Unable to parse payment Date for invoice " + invoice.getUniqueID());
				}
			}

		} catch (QueryException e) {
			logger.severe("Failed to get statistic: " + e.getMessage());
		}
		// Step 2: Sort the list based on the alphanumeric order of the keys
		completeMissingMonths();
		return stats;

	}

	/**
	 * Hilfsmethode ergänzt die fehlenden Monate
	 * 
	 * @param yearMonths
	 * @return
	 */
	private void completeMissingMonths() {
		if (stats == null || stats.size() < 3) {
			return;
		}

		// Convert the list of strings to a list of LocalDate objects
		List<LocalDate> dates = new ArrayList<>();
		Set<String> yearMonths = this.stats.keySet();
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");
		for (String yearMonth : yearMonths) {
			dates.add(YearMonth.parse(yearMonth, formatter).atDay(1));
		}

		// Find the lowest and highest dates
		LocalDate lowestDate = Collections.min(dates);
		LocalDate highestDate = Collections.max(dates);

		LocalDate current = lowestDate;
		current = current.plusMonths(1);
		while (!current.isAfter(highestDate)) {

			// existiert der monat?
			String formattedDate = current.format(formatter);
			DebitorStatistikData entry = stats.get(formattedDate);
			if (entry == null) {
				// add missing entry
				stats.put(formattedDate, new DebitorStatistikData(current.getYear(), current.getMonthValue()));
			}
			current = current.plusMonths(1);

		}

	}
	/**
	 * returns
	 * 
	 * 202304 from a given date
	 */
	public String getYearMonth(Date date) {

		// Create a Calendar instance and set the date
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);

		// Get the year from the Calendar object
		int year = calendar.get(Calendar.YEAR);
		// Get the month from the Calendar object
		int month = calendar.get(Calendar.MONTH);
		// Increment the month by 1 since Calendar months are zero-based
		month++;
		// Convert the month to a String with leading "0" if necessary
		return "" + year + (month < 10 ? "0" + month : "" + month);

	}

	/**
	 * Finds the payment.date for a invoice.
	 * 
	 * Wir suchen alle zugeordneten Zahlungseingägne und nehmen den letzten.
	 * 
	 * WICHTIG: Es muss ggf. der index neu aufgebaut werden, da payment.date nun ein
	 * index feld ist
	 * 
	 * @param invoice
	 * @return
	 * @throws ParseException
	 */
	public Date findPaymentDateByWorkitem(ItemCollection invoice) throws ParseException {

		// Wir selektieren alle Zahlungseingänge interessieren usn aber nur für den
		// letzten
		String sQuery = " (type:\"workitem\" OR type:\"workitemarchive\") " + //
				" AND ($workflowgroup:Zahlungseingang) AND ($workitemref:\""
				+ invoice.getUniqueID() + "\" )";

		List<ItemCollection> workitems = null;

		try {
			workitems = documentService.findStubs(sQuery, 99, 0,
					"payment.date", true);
			if (workitems.size() > 0) {
				return workitems.get(0).getItemValueDate("payment.date");
			}
		} catch (QueryException e) {

			e.printStackTrace();
		}

		// no date found!
		return null;

	}

	/**
	 * Finds the date when a workitem last reached the current task 5900 by reading
	 * the eventLog
	 * 
	 * This method is not so save as the method findPaymentDateByWorkitem that is
	 * searching the payment workitem.
	 * 
	 * 2023-06-28T12:40:17.437|rechnungsausgang-de-1.0|5001.50|5900|
	 * 
	 * @param invoice
	 * @return
	 * @throws ParseException
	 */
	public Date findPaymentDateByLog(ItemCollection invoice) throws ParseException {

		// Create a SimpleDateFormat instance with the desired format
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
		List<String> logEntries = invoice.getItemValue("$eventlog");

		// Iterate through the List from the last element to the first
		for (int i = logEntries.size() - 1; i >= 0; i--) {
			String entry = logEntries.get(i);

			if (entry.endsWith(invoice.getTaskID() + "|")) {
				// found!
				String dateString = entry.substring(0, entry.indexOf("|") - 1);
				// Parse the date string into a Date object
				Date date = sdf.parse(dateString);
				// Print the Date object
				System.out.println("Date: " + date);
				return date;
			}
		}

		// no date found!
		return null;

	}

	/**
	 * Diese Methode baut die Datenstruktur für das Chart Diagram zusammen
	 * 
	 * 
	 * <pre>
		{
		 labels: ["January", "February", "March", "April", "May", "June", "July"],
		 datasets: [{
			label: 'Dataset 1',
			//backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
			//borderColor: window.chartColors.red,
			borderWidth: 1,
			data: [
				70, 70, 70, 70, 79, 50, 50
			]
		}, {
			label: 'Dataset 2',
			//backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
			//borderColor: window.chartColors.blue,
			borderWidth: 1,
			data: [
				70, 70, 170, 7, 79, 50, 50
			]
		}]
	   }
	 * </pre>
	 * 
	 * @return
	 */
	public String buildChartData() {

		getStats();

		// build a list of all lables....
		List<String> statusLabels = new ArrayList<String>();

		Set<String> keys = stats.keySet();
		for (String _key : keys) {
			statusLabels.add(_key);
		}

		String result = "{";

		// Lables
		result = result + "labels : [ ";
		result = result + statusLabels.stream().collect(Collectors.joining("','", "'", "'"));
		result = result + "],";
		result = result + "datasets: [";

		// Datasets 1
		result = result + "{label: 'Zahlungsziel',borderWidth: 1,";
		result = result + " borderColor: [\"#3B6B82\"],";
		result = result + " \"backgroundColor\" : [\"#CFE9F5\"],  fill: true,tension: 0.5,";

		result = result + "data: [";
		for (Map.Entry<String, DebitorStatistikData> entry : stats.entrySet()) {
			result = result + entry.getValue().getAverageDueDays() + ",";
		}
		// cut last comma
		result = result.substring(0, result.length() - 1);
		result = result + "]";
		result = result + "}, ";

		// Datasets 2
		result = result + "{label: 'Zahldauer',borderWidth: 1,";
		result = result + " borderColor: [\"#E73B65\"],\"backgroundColor\" : [\"#70B088\" ], tension: 0.5,fill: true,";

		result = result
				+ " trendlineLinear: { colorMin: \"red\", colorMax: \"green\", lineStyle: \"dotted\", width: 2 , projection: true },";


		result = result + "data: [";
		for (Map.Entry<String, DebitorStatistikData> entry : stats.entrySet()) {
			result = result + entry.getValue().getAveragePaymentDays() + ",";
		}
		// cut last comma
		result = result.substring(0, result.length() - 1);
		result = result + "]";
		result = result + "} ";

		// ende
		result = result + "] }";

		return result;
	}

}
