package de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month;

import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.CLT_REC;
import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.CLT_REC_SRC;
import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.COLLECTOR;
import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.METER;
import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.OMS_REC;
import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.READING_COMPANY;
import static de.am_soft.sm_mtg.backend.daos.db.jooq.Tables.REAL_ESTATE;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.MOST_LIKELY_RE_AND_CLT_PER_METER_LID;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.RC_PER_METER_LID;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.RECS_IN_TIME_PERIOD;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.RE_AND_CLT_PER_METER_LID;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.SUMMARY_WITH_TIME_PERIOD;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.SUMMARY_WO_TIME_PERIOD;
import static de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmSchema.TIME_PERIOD;

import java.sql.Timestamp;
import java.time.OffsetDateTime;
import java.util.stream.Stream;

import org.jooq.CommonTableExpression;
import org.jooq.Condition;
import org.jooq.CreateTableCommentStep;
import org.jooq.DatePart;
import org.jooq.Field;
import org.jooq.Operator;
import org.jooq.Record2;
import org.jooq.Record4;
import org.jooq.Record5;
import org.jooq.Record6;
import org.jooq.Record7;
import org.jooq.Record8;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;

import de.am_soft.sm_mtg.backend.daos.db.jooq.enums.MeterMfctCode;
import de.am_soft.sm_mtg.backend.daos.db.jooq.enums.MeterType;
import de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmJqHelper.RowsCounter;
import de.am_soft.sm_mtg.backend.daos.db.reports.meter_cnt.last_month.MclmJqHelper.SummaryRowToRecMapper;
import de.am_soft.sm_mtg.model.report.meter_cnt_last_month.MdMclmSummary;
import de.am_soft.sm_mtg.model.report.meter_cnt_last_month.MdMclmSummary.RecWoUserDetails;
import de.am_soft.util.backend.mgmt.MgResProvider;
import de.am_soft.util.jdbc.jooq.custom_fields.postgres.PgInterval;

/**
 * Übersicht Anzahl Messgeräte des letzten Monats.
 * <p>
 * Nachfolgend wird eine Übersicht der Anzahl der empfangenen Messgeräte des letzten Monats erzeugt,
 * da dieser Wert pro Ableseunternehmen als Abrechnungsgrundlage für die Rechnungsstellung dient.
 * Wichtig ist dabei, dass wirklich nur solche Messgeräte berücksichtigt werden, die auch wirklich
 * in diesem Zeitraum empfangen wurden. Wir ermitteln also alle Datensätze des letzten Monats und
 * zählen dann die Messgeräte, von denen diese stammen. Zum Schluss wird noch ein bisschen Zeug zum
 * Zeitraum etc. ausgegeben, weil die Ausgabe hier vorwiegend für Menschen gedacht ist und per Mail
 * durch die Gegend geschickt werden soll.
 * </p>
 * <p>
 * Der aktuelle Ansatz ist, alle Auswertungen anhand von LIDs für Messgeräte durchzuführen, wie im
 * Java-Backend sonst an vielen Stellen auch. Hintergrund ist, dass Messgeräte pro Liegenschaft über
 * mehrere Datensammler hinweg empfangen werden können, teilweise sogar über die Grenzen von
 * Liegenschafen hinweg. Wir fahren hier aktuell also einen globalen Ansatz und zählen Messgeräte
 * pro Ableseunternehmen, nicht mehr pro Datensammler. Letztendlich geht es hier ja auch primär um
 * die grundsätzliche Bezahlung eines Geräts und nicht, über welchen DatSam es empfangen wird.
 * </p>
 */
public abstract class MclmSummary extends MgResProvider
{
	/**
	 * Time period we are interested in.
	 * <p>
	 * The time period we are interested in is needed in at least two places, one is the retrieval
	 * of the records, the second is the output created for human readers.
	 * </p>
	 * @return Time period of interest.
	 */
	private	CommonTableExpression<Record2<OffsetDateTime, OffsetDateTime>>
			timePeriod()
	{
		Field<Timestamp> nowAsMonth	= DSL.trunc(DSL.now(), DatePart.MONTH);
		Field<Timestamp> startAtTs	= nowAsMonth.sub(new PgInterval("1 month"));
		Field<Timestamp> endAtTs	= nowAsMonth.sub(new PgInterval("0 month"));

		// Values need to be compared to clt_rec.captured_at, which is time zone-aware. At least in
		// Postgres "now()" should be the same already as well, but jOOQ seems to model a timestamp
		// without time zone for that function for some reason.
		Field<OffsetDateTime> startAt	= startAtTs	.cast(SQLDataType.OFFSETDATETIME);
		Field<OffsetDateTime> endAt		= endAtTs	.cast(SQLDataType.OFFSETDATETIME);

		return TIME_PERIOD.NAME
			.fields(TIME_PERIOD.COL_START_AT.getName(),
					TIME_PERIOD.COL_END_AT	.getName())
			.as(DSL.selectFrom(DSL.values(DSL.row(startAt, endAt))));
	}

	/**
	 * Retrieve the records.
	 * <p>
	 * Retrieve the records in the selected time period with all the data other statements most
	 * likely need later as well, like for mapping meter lids to collectors and such. We are joining
	 * the tables to get that data here anyway.
	 * </p>
	 * @return Telegrams in time period of interest.
	 */
	private	CommonTableExpression<Record8<Integer, String, String, Long, OffsetDateTime, MeterMfctCode, String, MeterType>>
			recsInTimePeriod()
	{
		// No further restrictions are a valid use case and not throwing then allows full control of
		// how to handle that. Subclasses could easily forget to check permissions as well, one can
		// not protect against such mistakes.
		Condition	optRestrictions = this.checkRecsInTimePeriod();
					optRestrictions = optRestrictions == null
										? DSL.trueCondition()
										: optRestrictions;

		return RECS_IN_TIME_PERIOD.NAME
			.fields
			(
				RECS_IN_TIME_PERIOD.COL_RC_USER_ID			.getName(),
				RECS_IN_TIME_PERIOD.COL_REAL_ESTATE_NR		.getName(),
				RECS_IN_TIME_PERIOD.COL_CLT_MAC				.getName(),
				RECS_IN_TIME_PERIOD.COL_CLT_REC_ID			.getName(),
				RECS_IN_TIME_PERIOD.COL_CLT_REC_CAPTURED_AT	.getName(),
				RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE		.getName(),
				RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL.getName(),
				RECS_IN_TIME_PERIOD.COL_METER_TYPE			.getName()
			)

			.as
			(
				DSL.select
				(
					READING_COMPANY.USER_ID,
					REAL_ESTATE.NUMBER,
					COLLECTOR.MAC_ADDRESS,
					CLT_REC.ID,
					CLT_REC.CAPTURED_AT,
					METER.MFCT_CODE,
					METER.READING_SERIAL,
					METER.TYPE
				)

				.from(TIME_PERIOD.NAME)
				.crossJoin(CLT_REC)

				.join(CLT_REC_SRC)		.on(CLT_REC_SRC.ID				.eq(CLT_REC.ID))
				.join(COLLECTOR)		.on(CLT_REC_SRC.COLLECTOR		.eq(COLLECTOR.ID))
				.join(REAL_ESTATE)		.on(COLLECTOR.REAL_ESTATE		.eq(REAL_ESTATE.ID))
				.join(READING_COMPANY)	.on(REAL_ESTATE.READING_COMPANY	.eq(READING_COMPANY.ID))
				.join(OMS_REC)			.on(CLT_REC.OMS_REC				.eq(OMS_REC.ID))
				.join(METER)			.on(OMS_REC.METER				.eq(METER.ID))

				.where
				(
					CLT_REC.CAPTURED_AT.between
					(
						TIME_PERIOD.COL_START_AT,
						TIME_PERIOD.COL_END_AT
					)

					.and(optRestrictions)
					.and(REAL_ESTATE.DELETED.isFalse())
					.and(METER.REPLACED_WITH.isNull())
				)
			);
	}

	/**
	 * Real estate and collector per meter-LID.
	 * <p>
	 * Meters might be received by multiple collectors in even different real estates, so we want to
	 * guess which collector and real estate is the most likely one. That are those from which they
	 * have been received the most or if equality is given among all, the most current. So this
	 * provides the necessary data by mapping all meter lids to their receiving real estates and
	 * collectors and telling the caller how often those have been received and when was the last
	 * one.
	 * </p>
	 * @return Real estates and collector per meter-LID.
	 */
	private	CommonTableExpression<Record7<MeterMfctCode, String, MeterType, String, String, Integer, OffsetDateTime>>
			reAndCltPerMeterLid()
	{
		return RE_AND_CLT_PER_METER_LID.NAME
			.fields
			(
				RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE			.getName(),
				RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL		.getName(),
				RE_AND_CLT_PER_METER_LID.COL_METER_TYPE					.getName(),
				RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR				.getName(),
				RE_AND_CLT_PER_METER_LID.COL_CLT_MAC					.getName(),
				RE_AND_CLT_PER_METER_LID.COL_METER_LID_CNT				.getName(),
				RE_AND_CLT_PER_METER_LID.COL_METER_LID_LAST_CAPTURED_AT	.getName()
			)

			.as
			(
				DSL.select
				(
					RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE,
					RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL,
					RECS_IN_TIME_PERIOD.COL_METER_TYPE,
					RECS_IN_TIME_PERIOD.COL_REAL_ESTATE_NR,
					RECS_IN_TIME_PERIOD.COL_CLT_MAC,
					DSL.count(),
					DSL.max(RECS_IN_TIME_PERIOD.COL_CLT_REC_CAPTURED_AT)
				)

				.from(RECS_IN_TIME_PERIOD.NAME)

				.groupBy
				(
					RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE,
					RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL,
					RECS_IN_TIME_PERIOD.COL_METER_TYPE,
					RECS_IN_TIME_PERIOD.COL_REAL_ESTATE_NR,
					RECS_IN_TIME_PERIOD.COL_CLT_MAC
				)
			);
	}

	/**
	 * Most likely real estate and collector per meter-LID.
	 * <p>
	 * Meters might be received by multiple collectors in even different real estates, so we want to
	 * guess which collector and real estate is the most likely one. That are those from which they
	 * have been received the most or if equality is given among all, the most current.
	 * </p>
	 * @return Most likely real estates and collector per meter-LID.
	 */
	private	CommonTableExpression<Record5<MeterMfctCode, String, MeterType, String, String>>
			mostLikelyReAndCltPerMeterLid()
	{
		return MOST_LIKELY_RE_AND_CLT_PER_METER_LID.NAME
			.fields
			(
				MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE		.getName(),
				MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL	.getName(),
				MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_METER_TYPE				.getName(),
				MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR			.getName(),
				MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_CLT_MAC				.getName()
			)

			.as
			(
				DSL.select
				(
					DSL.field
					(
						"DISTINCT ON ({0}, {1}, {2}) {0}",
						RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE.getDataType(),
						RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE,
						RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL,
						RE_AND_CLT_PER_METER_LID.COL_METER_TYPE
					),

					//reAndCltPerMeterLid.field(ReAndCltPerMeterLid.COL_METER_MFCT_CODE),
					RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL,
					RE_AND_CLT_PER_METER_LID.COL_METER_TYPE,
					RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR,
					RE_AND_CLT_PER_METER_LID.COL_CLT_MAC
				)

				.from(RE_AND_CLT_PER_METER_LID.NAME)

				.orderBy
				(
					RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE			.asc(),
					RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL		.asc(),
					RE_AND_CLT_PER_METER_LID.COL_METER_TYPE					.asc(),
					RE_AND_CLT_PER_METER_LID.COL_METER_LID_CNT				.desc(),
					RE_AND_CLT_PER_METER_LID.COL_METER_LID_LAST_CAPTURED_AT	.desc()
				)
			);
	}

	/**
	 * Map meter-LIDs to reading companies.
	 * <p>
	 * In former versions we grouped meters by collectors, which didn't take into account that some
	 * meters could be received by different collectors. This problem is even worse as some meters
	 * might be received by multiple collectors in different real estates. So we group meters now by
	 * their company. In theory even multiple companies might receive the same meters, but we accept
	 * that for now.
	 * </p>
	 * @return Reading company by their meter-LID.
	 */
	private	CommonTableExpression<Record4<MeterMfctCode, String, MeterType, Integer>>
			rcPerMeterLid()
	{
		return RC_PER_METER_LID.NAME
			.fields
			(
				RC_PER_METER_LID.COL_METER_MFCT_CODE		.getName(),
				RC_PER_METER_LID.COL_METER_READING_SERIAL	.getName(),
				RC_PER_METER_LID.COL_METER_TYPE				.getName(),
				RC_PER_METER_LID.COL_RC_USER_ID				.getName()
			)

			.as
			(
				DSL.select
				(
					RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE,
					RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL,
					RECS_IN_TIME_PERIOD.COL_METER_TYPE,
					RECS_IN_TIME_PERIOD.COL_RC_USER_ID
				)

				.from(RECS_IN_TIME_PERIOD.NAME)

				.groupBy
				(
					RECS_IN_TIME_PERIOD.COL_RC_USER_ID,
					RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE,
					RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL,
					RECS_IN_TIME_PERIOD.COL_METER_TYPE
				)

				.orderBy
				(
					RECS_IN_TIME_PERIOD.COL_RC_USER_ID.asc()
				)
			);
	}

	/**
	 * Summary without time period.
	 * <p>
	 * The dataset to provide the raw data of interest itself, without providing the time period
	 * which is mostly only useful for human readers.
	 * </p>
	 * @return Summary without time period.
	 */
	private	CommonTableExpression<Record4<Integer, String, String, Integer>>
			summaryWoTimePeriod()
	{
		Condition joinCond = DSL.condition
		(
			Operator.AND,
			MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE.eq
			(
				RC_PER_METER_LID.COL_METER_MFCT_CODE
			),

			MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL.eq
			(
				RC_PER_METER_LID.COL_METER_READING_SERIAL
			),

			MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_METER_TYPE.eq
			(
				RC_PER_METER_LID.COL_METER_TYPE
			)
		);

		return SUMMARY_WO_TIME_PERIOD.NAME
			.fields
			(
				SUMMARY_WO_TIME_PERIOD.COL_RC_USER_ID		.getName(),
				SUMMARY_WO_TIME_PERIOD.COL_REAL_ESTATE_NR	.getName(),
				SUMMARY_WO_TIME_PERIOD.COL_CLT_MAC			.getName(),
				SUMMARY_WO_TIME_PERIOD.COL_METER_CNT		.getName()
			)

			.as
			(
				DSL.select
				(
					RC_PER_METER_LID.COL_RC_USER_ID,
					MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR,
					MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_CLT_MAC,
					DSL.count().as(SUMMARY_WO_TIME_PERIOD.COL_METER_CNT)
				)

				.from(RC_PER_METER_LID.NAME)
				.join(MOST_LIKELY_RE_AND_CLT_PER_METER_LID.NAME).on(joinCond)

				.groupBy
				(
					RC_PER_METER_LID.COL_RC_USER_ID,
					MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR,
					MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_CLT_MAC
				)

				.orderBy
				(
					RC_PER_METER_LID.COL_RC_USER_ID,
					MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR,
					MOST_LIKELY_RE_AND_CLT_PER_METER_LID.COL_CLT_MAC
				)
			);
	}

	/**
	 * Summary with time period.
	 * <p>
	 * Add the time period we worked on for human readers to the already created result rows.
	 * </p>
	 * @return Summary with time period.
	 */
	private	CommonTableExpression<Record6<Integer, String, String, Integer, OffsetDateTime, OffsetDateTime>>
			summaryWithTimePeriod()
	{
		return SUMMARY_WITH_TIME_PERIOD.NAME
			.fields
			(
				SUMMARY_WITH_TIME_PERIOD.COL_RC_USER_ID		.getName(),
				SUMMARY_WITH_TIME_PERIOD.COL_REAL_ESTATE_NR	.getName(),
				SUMMARY_WITH_TIME_PERIOD.COL_CLT_MAC		.getName(),
				SUMMARY_WITH_TIME_PERIOD.COL_METER_CNT		.getName(),
				SUMMARY_WITH_TIME_PERIOD.COL_TP_START_AT	.getName(),
				SUMMARY_WITH_TIME_PERIOD.COL_TP_END_AT		.getName()
			)

			.as
			(
				DSL.select
				(
					SUMMARY_WO_TIME_PERIOD.COL_RC_USER_ID,
					SUMMARY_WO_TIME_PERIOD.COL_REAL_ESTATE_NR,
					SUMMARY_WO_TIME_PERIOD.COL_CLT_MAC,
					SUMMARY_WO_TIME_PERIOD.COL_METER_CNT,

					TIME_PERIOD.COL_START_AT,
					TIME_PERIOD.COL_END_AT
				)

				.from(SUMMARY_WO_TIME_PERIOD.NAME)
				.crossJoin(TIME_PERIOD.NAME)
			);
	}

	/**
	 * Summary with time period as temporary table.
	 * <p>
	 * Sometimes this report and the one one summing meters up only are used one after another and
	 * that one summing up simply uses the data this report creates as well. Sharing SQL itself is
	 * easy using CTEs, but it doesn't make too much sense to really calculate the same data twice
	 * shortly after another. While databases most likely cache things as well, that can't be as
	 * efficient as simply storing the data a bit longer, the only problem is how to forward CTE-
	 * only data to other queries, as CTEs are query-private. The solution is to simply use a
	 * temporary table and by using even the same name like is used for the CTE, we are as flexible
	 * as possible to easily switch between using the table or the CTE only. We don't even need to
	 * define different names and really reuse everything of the same named CTE instead.
	 * </p>
	 * @return Summary with time period as temporary table.
	 */
	private	CreateTableCommentStep
			cachedSummaryWithTimePeriod()
	{
		CommonTableExpression<?> timePeriod						= this.timePeriod();
		CommonTableExpression<?> recsInTimePeriod				= this.recsInTimePeriod();
		CommonTableExpression<?> reAndCltPerMeterLid			= this.reAndCltPerMeterLid();
		CommonTableExpression<?> rcPerMeterLid					= this.rcPerMeterLid();
		CommonTableExpression<?> mostLikelyReAndCltPerMeterLid	= this.mostLikelyReAndCltPerMeterLid();
		CommonTableExpression<?> summaryWoTimePeriod			= this.summaryWoTimePeriod();
		CommonTableExpression<?> summaryWithTimePeriod			= this.summaryWithTimePeriod();

		return DSL
			.createTemporaryTable(SUMMARY_WITH_TIME_PERIOD.NAME)
			.as
			(
				DSL	.with(timePeriod)
					.with(recsInTimePeriod)
					.with(reAndCltPerMeterLid)
					.with(rcPerMeterLid)
					.with(mostLikelyReAndCltPerMeterLid)
					.with(summaryWoTimePeriod)
					.with(summaryWithTimePeriod)
					.selectFrom(SUMMARY_WITH_TIME_PERIOD.NAME)
			)
			.onCommitDrop();
	}

	/**
	 * Check access permissions for actual data.
	 * <p>
	 * Subclasses need to implement this class to apply general permission checks for the same
	 * named CTE reading actual data. They are free to return additional restrictions to e.g. read
	 * only the data associated with the current reading company of the current user or such. Not
	 * all subclasses need such restrictions, though.
	 * </p>
	 * <p>
	 * Subclasses MUST NOT check if processing this report is allowed at all, that is left to higher
	 * levels to be able to combine read data of different reports to various reports.
	 * </p>
	 * @return {@code null} in case no restrictions are necessary, the restrictions otherwise.
	 */
	protected abstract Condition checkRecsInTimePeriod();

	/**
	 * CTOR simply forwarding resources available to the base.
	 *
	 * @param other
	 */
	protected MclmSummary(MgResProvider other)
	{
		super(other);
	}

	/**
	 * Cache summary with time period without actually querying it.
	 * <p>
	 * By distinguishing caching data only from actually querying it, we are able to support the
	 * use case of {@link MclmSummed} as well, which under some conditions needs to make sure that
	 * data to query is available at all, and don't lose anything ourself. It doesn't make any
	 * difference if we fetch rows from a temporary table or CTEs bound privately to a query.
	 * </p>
	 */
	void cacheSummaryWithTimePeriod()
	{
		this.getDbConn().getJooq().execute(this.cachedSummaryWithTimePeriod());
	}

	// TODO docs
	private Stream<RecWoUserDetails> readSummaryWithTimePeriod()
	{
		return this.getDbConn().getJooq().select
		(
			SUMMARY_WITH_TIME_PERIOD.COL_RC_USER_ID,
			SUMMARY_WITH_TIME_PERIOD.COL_REAL_ESTATE_NR,
			SUMMARY_WITH_TIME_PERIOD.COL_CLT_MAC,
			SUMMARY_WITH_TIME_PERIOD.COL_METER_CNT,
			SUMMARY_WITH_TIME_PERIOD.COL_TP_START_AT,
			SUMMARY_WITH_TIME_PERIOD.COL_TP_END_AT
		)

		.from(SUMMARY_WITH_TIME_PERIOD.NAME)
		.fetchSize(MclmJqHelper.FETCH_SIZE)
		.stream()
		.map((row) -> new SummaryRowToRecMapper().apply(row));
	}

	// TODO docs
	public MdMclmSummary getRecords()
	{
		this.cacheSummaryWithTimePeriod();

		return new MdMclmSummary
		(
			this.readSummaryWithTimePeriod(),
			new RowsCounter().apply
			(
				this.getDbConn().getJooq(),
				SUMMARY_WITH_TIME_PERIOD.NAME
			)
		);
	}
}
