package com.capgemini.reports.execution;

import com.capgemini.framework.common.SettingsHelper;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.policies.PolicyLocation;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.ReportLaunchTypeEnum;
import com.capgemini.reports.builder.ReportBuilder;
import com.capgemini.reports.database.manager.MngCriteriaVal.CriteriaValue;
import com.capgemini.reports.execution.mapconstructor.IMapConstructor;
import com.capgemini.reports.execution.strategies.DelayedExecutionStrategy;
import com.capgemini.reports.execution.strategies.IExecutionStrategy;
import com.capgemini.reports.execution.strategies.ImmediateExecutionStrategy;
import com.capgemini.reports.execution.strategies.ScheduledExecutionStrategy;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.printing.Printer;
import com.capgemini.reports.printing.Printer.AvailablePrinter;
import com.capgemini.reports.printing.Printer.PrintFormat;
import com.capgemini.reports.queuing.ReportPriority;
import com.capgemini.reports.reports.parameters.IChecker;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.exception.ZipException;

import java.io.Serializable;
import java.util.List;
import java.util.UUID;

/**
 * Abstract class for the execution of each report. <br>
 * The main method, {@link #doStart(TcSession)}, is called by the queuing system and executes (and prints) a report. <br>
 * It uses in that order the following objects for processing:
 * <ol>
 * <li>Obtain the data from the Tc servers</li>
 * <li>Create the template</li>
 * <li>Printing</li>
 * </ol>
 * <p>
 * If any of these steps fail, then a mail is Sent.
 *
 * @param <T> Generic object about type of report
 * @param <U> A custom (POJO) object to carry data from the data preparator to the printer
 */
@Slf4j
@Getter
@Setter
@EqualsAndHashCode
public abstract class AbstractExecutionReport<T extends AbstractExecutionReport<T, U>, U> implements Serializable {
    // Configuration
    protected static final String DEFAULT_REVISION_RULE = SettingsHelper.getDefaultRevisionRule();
    private static final long serialVersionUID = 5813503218626640137L;
    private static final String LINE_BREAKER = "===========================================";
    protected final int priorityCode;
    protected final String revisionRule;
    protected String reportTechnicalUid = UUID.randomUUID().toString();
    // Scheduling parameters (default null if it is not a scheduling and it shoudn't be called). Setter is specifically added for both values
    @Setter(value = AccessLevel.NONE)
    protected String schedulingName;
    @Setter(value = AccessLevel.NONE)
    protected Long schedulingId;
    @Getter(value = AccessLevel.NONE)
    protected Boolean wkf = Boolean.FALSE; // By default, not launched by a WKF
    // Times parameters
    private String postingTime;
    private String startTime;
    @Setter(value = AccessLevel.NONE)
    private String endTime;
    @Setter(value = AccessLevel.NONE)
    private String failureTime;
    /**
     * The strategy which defines immediate, delayed or scheduled behaviors during the execution of a report. Set up during constructor, but can be overwritten by scheduling servlet to force scheduling behaviour.
     */
    private IExecutionStrategy executionStrategy;

    /**
     * Constructor for DELAYEd / IMMEDIATE / SCHEDULED launches.
     *
     * @param priorityCode     int - the priority code found in the reporting.properties file associated to a report.
     * @param reportLaunchType the type of launch required.ccan be {@link ReportLaunchTypeEnum#IMMEDIATE} or {@link ReportLaunchTypeEnum#DELAYED}
     */
    protected AbstractExecutionReport(final int priorityCode, final String revisionRule, final ReportLaunchTypeEnum reportLaunchType) {
        this.priorityCode = priorityCode;
        this.revisionRule = revisionRule;
        switch (reportLaunchType) {
            case DELAYED:
                this.executionStrategy = new DelayedExecutionStrategy();
                break;
            case IMMEDIATE:
                this.executionStrategy = new ImmediateExecutionStrategy();
                break;
            case SCHEDULED:
                this.executionStrategy = new ScheduledExecutionStrategy();
                break;
            default:
                break;
        }
    }


    /**
     * Constructor for DELAYEd / IMMEDIATE / SCHEDULED launches.
     *
     * @param priorityCode     int - the priority code found in the reporting.properties file associated to a report.
     * @param reportLaunchType the type of launch required.ccan be {@link ReportLaunchTypeEnum#IMMEDIATE} or {@link ReportLaunchTypeEnum#DELAYED}
     */

    public AbstractExecutionReport (final int priorityCode, ReportLaunchTypeEnum reportLaunchType) {
        this.priorityCode = priorityCode;
        this.revisionRule = null;
        switch (reportLaunchType) {
            case DELAYED:
                this.executionStrategy = new DelayedExecutionStrategy();
                break;
            case IMMEDIATE:
                this.executionStrategy = new ImmediateExecutionStrategy();
                break;
            case SCHEDULED:
                this.executionStrategy = new ScheduledExecutionStrategy();
                break;
            default:
                break;
        }
    }

    /* ******************************************/
    /* *************** EXECUTION ****************/
    /* ******************************************/

    /**
     * Defines the backbone of report creation.<br>
     * <ol>
     * In order of execution:
     * <li> : Extracts all the raw data from Teamcenter</li>
     * <li>{@link IMapConstructor#makeMasterTemplate(AbstractExecutionReport, Object) constructDataMaps}: Creates a {@link PrintParameters} objec</li>
     * <li>{@link IMapConstructor#constructDataMaps(TcSession, PrintParameters, Object) constructDataMaps}: Filters and maps the data to some tokens from the report template</li>
     * <li>Gives {@link PrintParameters} to that {@link Printer}</li>
     * <li>{@link Printer#print(PrintParameters) Print}s the report</li>
     * </ol>
     * Also, whenever any {@link KurtException} generated is caught here and will send an error email to the user.
     * <p>
     *
     * @param session the Teamcenter {@link com.capgemini.framework.teamcenter.authent.TcSession} used to perform the work.
     * @throws KurtException something goes wrong internally (database not in line, problems in TC communication, problems in repor generation, etc.)
     */
    public void doStart(final TcSession session) throws KurtException, ZipException {
        @SuppressWarnings("unchecked")
        T thisReport = (T) this;

        // Register the start of the report
        log.info("Job started on: " + getReportIdentifier().buildCompleteReportName());

        // Perform pre-execution steps
        executionStrategy.preExecutionStep(thisReport, session);

        String fileNameWithExtension = getDataPreparator().print(session, thisReport, executionStrategy);

        // All the users who requested this report, at this time of the execution: END
        executionStrategy.postExecutionStep(thisReport, fileNameWithExtension, session);

        log.info("Execution complete on: {}", this.getReportIdentifier().getReportTitle());
    }

    /**
     * Fills the scheduling data in case this report was created from a scheduling template.
     *
     * @param schedulingId   the Identifier of the scheduling
     * @param schedulingName the name of the scheduling
     */
    public void setScheduling(final Long schedulingId, final String schedulingName) {
        this.schedulingId = schedulingId;
        this.schedulingName = schedulingName;
    }

    /**
     * Get the data preparator.<br>
     * This object is responsible for recovering TC data and trnsforming it into report data.
     *
     * @return the {@link IDataReportPreparator} corresponding to the report
     */
    public abstract IDataReportPreparator<T> getDataPreparator();

    /**
     * Returns a list containing every {@link IChecker} used to verify report parameters BEFORE posting it to the queues.
     *
     * @return a {@link List}&lt;{@link IChecker}&gt;
     */
    public abstract List<IChecker> getQuickChecks();

    /**
     * This method provides the {@link PolicyLocation} of the property policy to be used during the long checks.
     *
     * @return the location of the file as a String
     */
    public abstract PolicyLocation getPropertyPolicyForLongChecks();

    /**
     * The identifier of the {@link PolicyLocation} for the resolution of nomenclature.
     *
     * @return identifier of the xml file
     */
    public abstract PolicyLocation getFullPolicy();

    /**
     * Returns a list containing every {@link IChecker} used for to verify report parameters AFTER posting it to the queues, but BEFORE the full execution begins.
     *
     * @return a {@link List}&lt;{@link IChecker}&gt;
     */
    public abstract List<IChecker> getLongChecks();

    /* ******************************************/
    /* **************** PRINTING ****************/
    /* ******************************************/

    /**
     * Returns some Printer.
     *
     * @return the available printer
     */
    public Printer getPrinter() {
        return new Printer(AvailablePrinter.JASPER);
    }

    /**
     * Returns the report priority (to know which queue to join).
     *
     * @return a {@link ReportPriority}
     */
    public ReportPriority getReportPriority() {
        return ReportPriority.getReportPriority(priorityCode);
    }

    /**
     * Returns the location to the JASPER template file to be used.
     *
     * @return the path (within src/main/resources)
     */
    public abstract String getTemplateLocation();

    /* ======================================== */
    /* = REPORT IDENTIFIERS, NAMES AND TITLES = */
    /* ======================================== */

    /**
     * Returns the report identifier.<br>
     * Exemple with Report R01: "REPORT_R01".
     *
     * @return the report code
     */
    public abstract ReportEnum getReportIdentifier();

    /**
     * Set true if this report has been launched by a workflow.
     */
    public void isWkf() {
        this.wkf = Boolean.TRUE;
    }

    /* ================ */
    /* = PRINT PARAMS = */
    /* ================ */

    /**
     * Returns the file name required to save this report, as per the specifications.
     *
     * @return the file name
     */
    public abstract String getSpecifiedFileName();

    /**
     * Returns the file name as it will be saved to disc locally.
     *
     * @return the file name
     */
    public String getFileNameOnDisc() {
        return getSpecifiedFileName() + "_" + reportTechnicalUid;
    }

    /**
     * Returns a structure of print parameters, initialised with the required output format for the report (xlsx, .pdf etc).
     *
     * @return some {@link PrintParameters}
     */
    public abstract PrintFormat getPrintingFormat();

    /**
     * Prepare criteria values list of report.
     *
     * @return a {@link List} of every {@link CriteriaValue}.
     */
    public abstract List<CriteriaValue> getLaunchData();

    /**
     * Returns the name of a {@link ReportBuilder} implementation that is able to instantiate a report of the right type.
     *
     * @return the full class name
     */
    public final String getReportBuilderClassName() {
        Class<? extends ReportBuilder> builderClass = getReportBuilderClass();
        return builderClass.getName();
    }

    /**
     * Returns the {@link Class} of a {@link ReportBuilder} implementation that is able to instantiate a report of the right type.
     *
     * @return the Class object. Must have a no-args constructor
     */
    public abstract Class<? extends ReportBuilder> getReportBuilderClass();

    /**
     * Sets the time at which this report execution failed.
     *
     * @param aFailure the Time at which it failed as a String
     */
    public void setFailureTime(final String aFailure) {
        this.failureTime = aFailure;
    }

    /**
     * Sets the time at which this report execution completed.
     *
     * @param aEndtime the Time at which it failed as a String
     */
    public void setEndTime(final String aEndtime) {
        this.endTime = aEndtime;
    }

    /**
     * Clone a report execution from a scheduling template.<br>
     * Give him all the same criteria but a different uid. Also, reset the default launch type.
     *
     * @return the cloned report
     */
    public abstract AbstractExecutionReport<?, ?> cloneTemplateToExecution();

    /**
     * Returns the {@link ReportLaunchTypeEnum} planned for this report.
     *
     * @return the {@link ReportLaunchTypeEnum}
     */
    public final ReportLaunchTypeEnum getLaunchType() {
        return getExecutionStrategy().getLaunchType();
    }

}
