package de.jottyfan.camporganizer.module.confirmation.confirmation;

import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSONDOCUMENT;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_RSS;
import static de.jottyfan.camporganizer.db.jooq.Tables.V_CAMP;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertValuesStep2;
import org.jooq.Name;
import org.jooq.Record4;
import org.jooq.Record5;
import org.jooq.Record7;
import org.jooq.Record8;
import org.jooq.SelectConditionStep;
import org.jooq.SelectHavingStep;
import org.jooq.SelectSeekStep1;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumModule;
import de.jottyfan.camporganizer.db.jooq.enums.EnumProgress;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersondocumentRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TRssRecord;
import de.jottyfan.camporganizer.module.camplist.model.LambdaResultWrapper;
import de.jottyfan.camporganizer.module.confirmation.confirmation.model.BookingBean;
import de.jottyfan.camporganizer.module.confirmation.confirmation.model.CampOverviewBean;

/**
 *
 * @author jotty
 *
 */
@Repository
@Transactional(transactionManager = "transactionManager")
public class ConfirmationRepository {
	private static final Logger LOGGER = LogManager.getLogger(ConfirmationRepository.class);

	@Autowired
	private DSLContext jooq;

	/**
	 * get an overview of camps
	 *
	 * @param currentUser the current user to check for its privileges
	 * @return a list of camp overview beans
	 */
	public List<CampOverviewBean> getCampOverviewBeans(String currentUser) {
		Name COUNT = DSL.name("count");
		SelectHavingStep<Record4<Integer, EnumProgress, String, LocalDateTime>> sql = jooq
		// @formatter:off
			.select(DSL.count(T_PERSON.PK).as(COUNT),
					    T_PERSON.PROGRESS,
					    T_CAMP.NAME,
					    T_CAMP.ARRIVE)
			.from(T_PERSON)
			.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
			.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
			.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
			.where(T_CAMP.ARRIVE.isNotNull())
			.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
			.and(T_PROFILE.USERNAME.eq(currentUser))
			.groupBy(T_CAMP.NAME, T_CAMP.ARRIVE, T_PERSON.PROGRESS);
		// @formatter:on
		LOGGER.trace(sql);
		Map<LocalDateTime, CampOverviewBean> map = new HashMap<>();
		Iterator<Record4<Integer, EnumProgress, String, LocalDateTime>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record4<Integer, EnumProgress, String, LocalDateTime> r = i.next();
			Integer count = r.get(COUNT, Integer.class);
			EnumProgress progress = r.get(T_PERSON.PROGRESS);
			String campname = r.get(T_CAMP.NAME);
			LocalDateTime arrive = r.get(T_CAMP.ARRIVE);
			CampOverviewBean bean = map.get(arrive);
			if (bean == null) {
				bean = new CampOverviewBean(arrive.toLocalDate(), campname);
				map.put(arrive, bean);
			}
			if (progress == null || EnumProgress.requested.equals(progress)) {
				bean.setUntouched(count);
			} else if (EnumProgress.approved.equals(progress)) {
				bean.setApproved(count);
			} else if (EnumProgress.rejected.equals(progress)) {
				bean.setRejected(count);
			} else if (EnumProgress.revoked.equals(progress)) {
				bean.setRevoked(count);
			}
		}
		List<CampOverviewBean> list = new ArrayList<>(map.values());
		Collections.sort(list);
		return list;
	}

	/**
	 * get all bookings with condition that this user can manage
	 *
	 * @param currentUser the current user
	 * @param condition   the condition e.g. T_PERSON.ACCEPT.isNull()
	 * @return a list of booking beans; an empty one at least
	 */
	private List<BookingBean> getListWithCondition(String currentUser, Condition condition) {
		SelectSeekStep1<Record7<Integer, String, String, String, LocalDateTime, EnumCamprole, LocalDateTime>, LocalDateTime> sql = jooq
		// @formatter:off
			.select(T_PERSON.PK, T_PERSON.FORENAME, T_PERSON.SURNAME, T_CAMP.NAME, T_CAMP.ARRIVE, T_PERSON.CAMPROLE, T_PERSON.CREATED)
			.from(T_PERSON)
			.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
			.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
			.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
			.where(condition)
			.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
			.and(T_PROFILE.USERNAME.eq(currentUser))
			.orderBy(T_PERSON.CREATED.desc());
		// @formatter:on
		LOGGER.trace(sql);
		List<BookingBean> list = new ArrayList<>();
		Iterator<Record7<Integer, String, String, String, LocalDateTime, EnumCamprole, LocalDateTime>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record7<Integer, String, String, String, LocalDateTime, EnumCamprole, LocalDateTime> r = i.next();
			String forename = r.get(T_PERSON.FORENAME);
			String surname = r.get(T_PERSON.SURNAME);
			String campname = r.get(T_CAMP.NAME);
			LocalDateTime arrive = r.get(T_CAMP.ARRIVE);
			EnumCamprole role = r.get(T_PERSON.CAMPROLE);
			LocalDateTime created = r.get(T_PERSON.CREATED);
			Integer pkPerson = r.get(T_PERSON.PK);
			BookingBean bean = new BookingBean(pkPerson, arrive.toLocalDate(), campname);
			bean.setRole(role == null ? null : role.getLiteral());
			bean.setRegistered(created);
			bean.setFullname(new StringBuilder().append(forename).append(" ").append(surname).toString());
			list.add(bean);
		}
		return list;
	}

	/**
	 * get all untouched bookings that this user can manage
	 *
	 * @param currentUser the current user
	 * @return a list of booking beans; an empty one at least
	 */
	public List<BookingBean> getUntouched(String currentUser) {
		return getListWithCondition(currentUser, T_PERSON.PROGRESS.eq(EnumProgress.requested));
	}

	/**
	 * get all approved bookings that this user can manage
	 *
	 * @param currentUser the current user
	 * @return a list of booking beans; an empty one at least
	 */
	public List<BookingBean> getApproved(String currentUser) {
		return getListWithCondition(currentUser, T_PERSON.PROGRESS.eq(EnumProgress.approved));
	}

	/**
	 * get all rejected bookings that this user can manage
	 *
	 * @param currentUser the current user
	 * @return a list of booking beans; an empty one at least
	 */
	public List<BookingBean> getRejected(String currentUser) {
		return getListWithCondition(currentUser, T_PERSON.PROGRESS.eq(EnumProgress.rejected));
	}

	/**
	 * get all revoked bookings that this user can manage
	 *
	 * @param currentUser the current user
	 * @return a list of booking beans; an empty one at least
	 */
	public List<BookingBean> getRevoked(String currentUser) {
		return getListWithCondition(currentUser, T_PERSON.PROGRESS.eq(EnumProgress.revoked));
	}

	/**
	 * get the result of the search
	 *
	 * @param needle      the needle
	 * @param currentUser the current user
	 * @return a list of found beans; an empty one at least
	 */
	public List<BookingBean> getSearchResult(String needle, String currentUser) {
		Condition condition = T_PERSON.FORENAME.containsIgnoreCase(needle)
		// @formatter:off
				.or(T_PERSON.SURNAME.containsIgnoreCase(needle))
				.or(V_CAMP.NAME.containsIgnoreCase(needle))
				.or(V_CAMP.YEAR.cast(String.class).containsIgnoreCase(needle));
		// @formatter:on

		SelectSeekStep1<Record8<Integer, String, String, EnumCamprole, String, Double, String, EnumProgress>, LocalDateTime> sql = jooq
		// @formatter:off
			.select(T_PERSON.PK, T_PERSON.FORENAME, T_PERSON.SURNAME, T_PERSON.CAMPROLE, V_CAMP.NAME, V_CAMP.YEAR, V_CAMP.LOCATION_NAME, T_PERSON.PROGRESS)
			.from(T_PERSON)
			.leftJoin(V_CAMP).on(V_CAMP.PK.eq(T_PERSON.FK_CAMP))
			.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
			.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
			.where(condition)
			.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
			.and(T_PROFILE.USERNAME.eq(currentUser))
			.orderBy(T_PERSON.CREATED.desc());
		// @formatter:on
		LOGGER.trace(sql);
		List<BookingBean> list = new ArrayList<>();
		Iterator<Record8<Integer, String, String, EnumCamprole, String, Double, String, EnumProgress>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record8<Integer, String, String, EnumCamprole, String, Double, String, EnumProgress> r = i.next();
			Integer pkPerson = r.get(T_PERSON.PK);
			String forename = r.get(T_PERSON.FORENAME);
			String surname = r.get(T_PERSON.SURNAME);
			EnumCamprole role = r.get(T_PERSON.CAMPROLE);
			String campname = r.get(V_CAMP.NAME);
			Double year = r.get(V_CAMP.YEAR);
			EnumProgress progress = r.get(T_PERSON.PROGRESS);
			BookingBean bean = new BookingBean(pkPerson, null, String.format("%s %4.0f", campname, year));
			bean.setRole(role == null ? null : role.getLiteral());
			bean.setFullname(new StringBuilder().append(forename).append(" ").append(surname).toString());
			bean.setProgress(progress == null ? null : progress.getLiteral());
			list.add(bean);
		}
		return list;
	}

	/**
	 * remove the booking and all of its dependencies
	 *
	 * @param id the pk of t_person
	 * @return number of affected database rows, should be 1
	 */
	public Integer removeBooking(Integer id) {
		LambdaResultWrapper lrw = new LambdaResultWrapper();
		jooq.transaction(t -> {
			SelectConditionStep<Record5<String, String, String, String, LocalDateTime>> sql0 = DSL.using(t)
			// @formatter:off
				.select(T_PROFILE.USERNAME, T_PERSON.FORENAME, T_PERSON.SURNAME, T_CAMP.NAME, T_CAMP.ARRIVE)
				.from(T_PERSON)
				.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
				.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_PERSON.FK_PROFILE))
				.where(T_PERSON.PK.eq(id));
			// @formatter:on
			LOGGER.trace(sql0.toString());
			Record5<String, String, String, String, LocalDateTime> r = sql0.fetchOne();
			if (r == null) {
				throw new DataAccessException("no such entry in t_person with id = " + id);
			}
			String username = r.get(T_PROFILE.USERNAME);
			String forename = r.get(T_PERSON.FORENAME);
			String surname = r.get(T_PERSON.SURNAME);
			String campname = r.get(T_CAMP.NAME);
			LocalDateTime arrive = r.get(T_CAMP.ARRIVE);

			StringBuilder rssMessage = new StringBuilder(username);
			rssMessage.append(" hat die Stornierung der Buchung von ");
			rssMessage.append(forename).append(" ").append(surname);
			rssMessage.append(" an ");
			rssMessage.append(campname).append(" ")
					.append(arrive == null ? "" : arrive.format(DateTimeFormatter.ofPattern("YYYY")));
			rssMessage.append(" bestätigt und den Datensatz gelöscht.");

			DeleteConditionStep<TPersondocumentRecord> sql1 = DSL.using(t).deleteFrom(T_PERSONDOCUMENT)
					.where(T_PERSONDOCUMENT.FK_PERSON.eq(id));
			LOGGER.trace(sql1.toString());
			sql1.execute();

			DeleteConditionStep<TPersonRecord> sql2 = DSL.using(t).deleteFrom(T_PERSON).where(T_PERSON.PK.eq(id));
			LOGGER.trace(sql2.toString());
			lrw.add(sql2.execute());

			InsertValuesStep2<TRssRecord, String, String> sql3 = DSL.using(t)
			// @formatter:off
				.insertInto(T_RSS,
                    T_RSS.MSG,
                    T_RSS.RECIPIENT)
				.values(rssMessage.toString(), "registrator");
			// @formatter:on
			LOGGER.trace("{}", sql3.toString());
			sql3.execute();
		});
		return lrw.getCounter();
	}
}
