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

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_LOCATION;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_RSS;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.InsertValuesStep2;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.Record11;
import org.jooq.Record4;
import org.jooq.SelectConditionStep;
import org.jooq.SelectSeekStep1;
import org.jooq.UpdateConditionStep;
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.TProfile;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TRssRecord;
import de.jottyfan.camporganizer.module.camplist.model.LambdaResultWrapper;
import de.jottyfan.camporganizer.module.confirmation.person.model.CampBean;
import de.jottyfan.camporganizer.module.confirmation.person.model.PersonBean;
import de.jottyfan.camporganizer.module.mail.MailBean;
import de.jottyfan.camporganizer.module.mail.MailRepository;

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

	@Autowired
	private DSLContext jooq;

	@Autowired
	private MailRepository mailRepository;

	/**
	 * get all camps from the database if username is allowed to maintain it
	 *
	 * @param username the username of the requesting person
	 * @return all camps
	 */
	public List<CampBean> getCamps(String username) {
		SelectSeekStep1<Record4<Integer, String, LocalDateTime, String>, LocalDateTime> sql = jooq
		// @formatter:off
			.select(T_CAMP.PK, T_CAMP.NAME, T_CAMP.ARRIVE, T_LOCATION.NAME)
			.from(T_CAMP)
			.leftJoin(T_LOCATION).on(T_LOCATION.PK.eq(T_CAMP.FK_LOCATION))
			.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_CAMP.PK))
			.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
			.where(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
			.and(T_PROFILE.USERNAME.eq(username))
			.orderBy(T_CAMP.ARRIVE.desc());
		// @formatter:on
		LOGGER.trace(sql);
		List<CampBean> list = new ArrayList<>();
		Iterator<Record4<Integer, String, LocalDateTime, String>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record4<Integer, String, LocalDateTime, String> r = i.next();
			CampBean bean = new CampBean();
			bean.setPk(r.get(T_CAMP.PK));
			bean.setName(r.get(T_CAMP.NAME));
			bean.setArrive(r.get(T_CAMP.ARRIVE));
			bean.setLocation(r.get(T_LOCATION.NAME));
			list.add(bean);
		}
		return list;
	}

	/**
	 * get the person referenced by pk if username is allowed to read it
	 *
	 * @param username the username of the requesting person
	 * @param pk       the ID of the person
	 * @return the person bean if found; null otherwise
	 */
	public PersonBean getPerson(String username, Integer pk) {
		SelectConditionStep<Record> sql = jooq
		// @formatter:off
			.select(T_PERSON.fields())
			.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_PERSON.PK.eq(pk))
			.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
			.and(T_PROFILE.USERNAME.eq(username));
		// @formatter:on
		LOGGER.trace(sql);
		Iterator<Record> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record r = i.next();
			PersonBean bean = new PersonBean();
			bean.setPk(pk);
			EnumProgress progress = r.get(T_PERSON.PROGRESS);
			bean.setProgress(progress == null ? null : progress.getLiteral());
			bean.setBirthdate(r.get(T_PERSON.BIRTHDATE));
			bean.setCamprole(r.get(T_PERSON.CAMPROLE));
			bean.setCity(r.get(T_PERSON.CITY));
			bean.setComment(r.get(T_PERSON.COMMENT));
			bean.setCreated(r.get(T_PERSON.CREATED));
			bean.setEmail(r.get(T_PERSON.EMAIL));
			bean.setFkCamp(r.get(T_PERSON.FK_CAMP));
			bean.setFkProfile(r.get(T_PERSON.FK_PROFILE));
			bean.setFkRegistrator(r.get(T_PERSON.FK_REGISTRATOR));
			bean.setForename(r.get(T_PERSON.FORENAME));
			bean.setSurname(r.get(T_PERSON.SURNAME));
			bean.setPhone(r.get(T_PERSON.PHONE));
			bean.setSex(r.get(T_PERSON.SEX));
			bean.setStreet(r.get(T_PERSON.STREET));
			bean.setZip(r.get(T_PERSON.ZIP));
			bean.setRequiredPrice(r.get(T_PERSON.REQUIRED_PRICE));
			return bean;
		}
		return null;
	}

	/**
	 * update that bean in the database
	 *
	 * @param bean the bean
	 * @return the number of affected database rows
	 */
	public Integer updatePerson(PersonBean bean, String registrator) {
		LambdaResultWrapper lrw = new LambdaResultWrapper();
		jooq.transaction(t -> {

			SelectConditionStep<TProfileRecord> sql0 = jooq.selectFrom(T_PROFILE).where(T_PROFILE.USERNAME.eq(registrator));
			LOGGER.trace(sql0);
			Integer fkRegistrator = sql0.fetchOne(T_PROFILE.PK);

			// get old accept value for comparison
			SelectConditionStep<TPersonRecord> sql1 = jooq.selectFrom(T_PERSON).where(T_PERSON.PK.eq(bean.getPk()));
			LOGGER.trace(sql1);
			TPersonRecord r = sql1.fetchOne();
			lrw.putBoolean("acceptOld", r == null ? null : EnumProgress.approved.equals(r.getProgress()));
			lrw.putBoolean("acceptNew", EnumProgress.approved.getLiteral().equals(bean.getProgress()));
			Integer fkCamp = r == null ? null : r.getFkCamp();
			String email = r.getEmail(); // use the old one, too
			lrw.putString("oldEmail", email);

			SelectConditionStep<TCampRecord> sql2 = jooq.selectFrom(T_CAMP).where(T_CAMP.PK.eq(fkCamp));
			LOGGER.trace(sql2);
			TCampRecord rc = sql2.fetchOne();
			String campName = rc == null ? null : rc.getName();
			LocalDateTime arrive = rc == null ? null : rc.getArrive();
			String campNameWithYear = new StringBuilder(campName == null ? "" : campName).append(" ")
					.append(arrive == null ? "" : arrive.format(DateTimeFormatter.ofPattern("YYYY"))).toString();
			lrw.putString("campNameWithYear", campNameWithYear);

			UpdateConditionStep<TPersonRecord> sql3 = jooq
			// @formatter:off
				.update(T_PERSON)
				.set(T_PERSON.FORENAME, bean.getForename())
				.set(T_PERSON.SURNAME, bean.getSurname())
				.set(T_PERSON.STREET, bean.getStreet())
				.set(T_PERSON.ZIP, bean.getZip())
				.set(T_PERSON.CITY, bean.getCity())
				.set(T_PERSON.BIRTHDATE, bean.getBirthdate())
				.set(T_PERSON.SEX, bean.getSex())
				.set(T_PERSON.PHONE, bean.getPhone())
				.set(T_PERSON.EMAIL, bean.getEmail())
				.set(T_PERSON.COMMENT, bean.getComment())
				.set(T_PERSON.PROGRESS, EnumProgress.lookupLiteral(bean.getProgress()))
				.set(T_PERSON.CAMPROLE, bean.getCamprole())
				.set(T_PERSON.FK_REGISTRATOR, fkRegistrator)
				.set(T_PERSON.REQUIRED_PRICE, bean.getRequiredPrice())
				.where(T_PERSON.PK.eq(bean.getPk()));
			// @formatter:on
			LOGGER.trace(sql3);
			lrw.add(sql3.execute());

			// always
			StringBuilder buf = new StringBuilder("Eine Anmeldung für ");
			buf.append(campNameWithYear);
			buf.append(" wurde von ").append(registrator);
			buf.append(" korrigiert.");

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

		// send email to user instead of rss feed
		Boolean acceptNew = lrw.getBoolean("acceptNew");
		Boolean acceptOld = lrw.getBoolean("acceptOld");
		String campNameWithYear = lrw.getString("campNameWithYear");
		String email = lrw.getString("oldEmail");
		StringBuilder buf = new StringBuilder();
		String subject = "allgemeine Nachricht vom Buchungsportal der Onkel-Werner-Freizeiten";
		Boolean sendMail = false;
		if (acceptNew == null) {
			if (acceptOld != null) {
				subject = new StringBuilder("Deine Teilnahme an ").append(campNameWithYear).append(" wurde zurückgesetzt")
						.toString();
				buf = new StringBuilder("Die Bestätigung der Anmeldung von ");
				buf.append(bean.getForename());
				buf.append(" ");
				buf.append(bean.getSurname());
				buf.append(" zur Freizeit ");
				buf.append(campNameWithYear);
				buf.append(" wurde wieder zurückgezogen.");
				buf.append(
						" Möglicherweise wurde die Anmeldung versehentlich bestätigt? Deine Anmeldung befindet sich jetzt wieder auf der Warteliste.");
				sendMail = true;
			}
		} else if (acceptNew == true) {
			if (acceptOld == null || !acceptOld) {
				subject = new StringBuilder("Deine Teilnahme an ").append(campNameWithYear).append(" wurde bestätigt")
						.toString();
				buf = new StringBuilder("Die Anmeldung von ");
				buf.append(bean.getForename());
				buf.append(" ");
				buf.append(bean.getSurname());
				buf.append(" zur Freizeit ");
				buf.append(campNameWithYear);
				buf.append(" wurde bestätigt. Melde Dich jetzt unter https://www.onkelwernerfreizeiten.de an,");
				buf.append(" um die zu unterschreibenden Dokumente herunterzuladen, auszudrucken und uns zukommen zu lassen.");
				sendMail = true;
			}
		} else if (acceptNew == false) {
			if (acceptOld == null || acceptOld) {
				subject = new StringBuilder("Deine Teilnahme an ").append(campNameWithYear).append(" wurde abgelehnt")
						.toString();
				buf = new StringBuilder("Die Anmeldung von ");
				buf.append(bean.getForename());
				buf.append(" ");
				buf.append(bean.getSurname());
				buf.append(" zur Freizeit ");
				buf.append(campNameWithYear);
				buf.append(" wurde leider abgelehnt.");
				buf.append(
						" Möglicherweise ist sie schon ausgebucht? Deine Anmeldung befindet sich jetzt auf der Nachrückerliste.");
				sendMail = true;
			}
		}
		if (sendMail) {
			try {
				MailBean mailBean = new MailBean();
				mailBean.getTo().add(email);
				mailBean.getTo().add(bean.getEmail());
				mailBean.setSubject(subject);
				mailBean.setMessage(buf.toString());
				mailRepository.sendMail(mailBean); // no matter if the sending works
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
		return lrw.getCounter();
	}

	/**
	 * get annotations of person
	 *
	 * @param pk the ID of the person
	 * @return annotations if found; an empty string if not
	 */
	public String getAnnotations(Integer pk) {
		TProfile REGISTRATOR = T_PROFILE.as("registrator");
		SelectConditionStep<Record11<LocalDate, LocalDateTime, EnumCamprole, LocalDateTime, LocalDateTime, Integer, Integer, String, String, String, String>> sql = jooq
		// @formatter:off
			.select(T_PERSON.BIRTHDATE, T_PERSON.CREATED, T_PERSON.CAMPROLE,
							T_CAMP.ARRIVE, T_CAMP.DEPART,
							T_CAMP.MIN_AGE, T_CAMP.MAX_AGE,
							T_PROFILE.FORENAME, T_PROFILE.SURNAME,
							REGISTRATOR.FORENAME, REGISTRATOR.SURNAME)
			.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))
			.leftJoin(REGISTRATOR).on(REGISTRATOR.PK.eq(T_PERSON.FK_REGISTRATOR))
			.where(T_PERSON.PK.eq(pk));
		// @formatter:on
		LOGGER.trace(sql);
		StringBuilder buf = new StringBuilder();
		Iterator<Record11<LocalDate, LocalDateTime, EnumCamprole, LocalDateTime, LocalDateTime, Integer, Integer, String, String, String, String>> i = sql
				.fetch().iterator();
		while (i.hasNext()) {
			Record11<LocalDate, LocalDateTime, EnumCamprole, LocalDateTime, LocalDateTime, Integer, Integer, String, String, String, String> r = i
					.next();
			LocalDate birthdate = r.get(T_PERSON.BIRTHDATE);
			LocalDateTime arrive = r.get(T_CAMP.ARRIVE);
			LocalDateTime depart = r.get(T_CAMP.DEPART);
			Integer minAge = r.get(T_CAMP.MIN_AGE);
			Integer maxAge = r.get(T_CAMP.MAX_AGE);
			String bookerForename = r.get(T_PROFILE.FORENAME);
			String bookerSurname = r.get(T_PROFILE.SURNAME);
			String registratorForename = r.get(REGISTRATOR.FORENAME);
			String registratorSurname = r.get(REGISTRATOR.SURNAME);
			LocalDateTime created = r.get(T_PERSON.CREATED);
			EnumCamprole role = r.get(T_PERSON.CAMPROLE);
			String createdString = created.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
			if (bookerForename == null && bookerSurname == null) {
				buf.append(String.format("angemeldet ohne Registrierung am %s\n", createdString));
			} else {
				buf.append(String.format("angemeldet von %s %s am %s\n", bookerForename, bookerSurname, createdString));
			}
			if (registratorForename != null || registratorSurname != null) {
				buf.append(String.format("bearbeitet von %s %s\n", registratorForename, registratorSurname));
			}
			if (birthdate == null) {
				buf.append("Geburtstag wurde nicht angegeben\n");
			} else if (arrive.isBefore(birthdate.atStartOfDay()) && depart.isAfter(birthdate.atStartOfDay())) {
				buf.append("hat während der Freizeit Geburtstag\n");
			}
			if (birthdate == null || arrive == null) {
				buf.append("Alter konnte wegen fehlendem Geburtstag nicht ermittelt werden\n");
			} else {
				long age = ChronoUnit.YEARS.between(birthdate, arrive);
				if (EnumCamprole.student.equals(role) && (minAge > age || maxAge < age)) {
					buf.append("passt vom Alter her nicht in die Freizeit");
				}
			}
		}
		return buf.toString();
	}

	/**
	 * get the price information of the camp that the person is registered for
	 *
	 * @param pkPerson the ID of the person
	 * @return the camp price information
	 */
	public String getCampprice(Integer pkPerson) {
		SelectConditionStep<Record1<String>> sql = jooq
		// @formatter:off
			.select(T_CAMP.PRICE)
			.from(T_PERSON)
			.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
			.where(T_PERSON.PK.eq(pkPerson));
		// @formatter:on
		LOGGER.trace(sql);
		return sql.fetchOne(T_CAMP.PRICE);
	}
}
