package com.capgemini.reports.quartz.jobs;

import com.capgemini.framework.common.DateHelper;
import com.capgemini.framework.common.FileHelper;
import com.capgemini.framework.common.SettingsHelper;
import com.capgemini.framework.common.StringHelper;
import com.capgemini.reports.database.manager.MngReport;
import com.capgemini.reports.database.manager.MngReport.RemovedReportEntry;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/** Class managing the purge jobs for deleting old report files. */
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Slf4j
// Required for Quartz
@NoArgsConstructor
public class PurgeJob implements Job {

    private final boolean isDevMode = SettingsHelper.isDevMode();

    /** CRON expression for when the schedule should trigger at every instance of this field (every month / every year etc. */
    private static final String CRON_EVERY = "*";
    /** CRON expression for when the schedule should not take the field into consideration (no month constraint, or no day in week constraints etc). */
    private static final String CRON_DOES_NOT_MATTER = "?";

    private static final boolean FAIL_ON_RECOVERY = true;

    private static final String HUMAN_DESCRIPTION_FOR_PURGE = "Purge des rapports classiques et planfies";
    private static final String JOB_NAME = "job_purge";
    private static final String JOB_GROUP_NAME = "purge_group_name";
    private static final String STR_COLON = ":";

    // Properties for report and schedulings
    private static final int REPORT_NB_DAYS = SettingsHelper.getPurgeDelayReports();
    private static final int SCHEDULING_REPORT_NB_DAYS = SettingsHelper.getPurgeDelayScheduling();
    private static final File REPORT_OUTPUT_FOLDER = new File(SettingsHelper.getReportLocation());
    private static final File SCHEDULING_REPORT_OUTPUT_FOLDER_HISTORY = new File(SettingsHelper.getSchedulingHistoryLocation());

    private static final String FORMAT_DATE = "dd/MM/yyyy HH:mm";

    private static final CronScheduleBuilder CRON_SCHEDULE;

    static {
        // Configuration for purge launch type (Pattern HH:mm)
        String startTimePurge = SettingsHelper.getPurgeHour();
        String purgeSecond = "0";
        String purgeMinute = startTimePurge.split(STR_COLON)[1];
        String purgeHour = startTimePurge.split(STR_COLON)[0];

        // Cron trigger for purge job
        String fullCron = StringHelper.implode(" ", purgeSecond, purgeMinute, purgeHour, CRON_EVERY, CRON_EVERY, CRON_DOES_NOT_MATTER, CRON_EVERY);

        // Space-separated cron expressions
        CRON_SCHEDULE = CronScheduleBuilder.cronSchedule(fullCron);
    }

    @Override
    public void execute(final JobExecutionContext jobExecutionContext) throws JobExecutionException {
        if (isDevMode) {
            return; // Skip
        }
        try {
            log.info("############################################################################################################");
            log.info("Démarrage de la purge le {}", DateHelper.getDate());
            purgeReportsBefore();
            purgeScheduledReportsBefore();
            // write log to directory
            log.info("La purge s'est terminee avec succès....");
            log.info("############################################################################################################");
        } catch (Exception e) {
            log.error("An exception occured during purge ", e);
        }
    }

    private void purgeReportsBefore() {
        Calendar deadlineReports = DateHelper.offsetDays(-REPORT_NB_DAYS);
        log.info("Nombre de jours définis pour les rapports classiques [" + REPORT_NB_DAYS + "] ");
        log.info("Démarrage de la purge des rapports classiques terminés avant le {}", new SimpleDateFormat().format(deadlineReports.getTime()));

        // Delete related data from DB
        log.info("Suppression des entrées dans la base de données");
        List<RemovedReportEntry> removed = new MngReport().deleteReportsBeforeDate(deadlineReports);
        for (RemovedReportEntry removedReportsData : removed) {
            log.info("\tTrouvé {} datant du [{}], nom du fichier[{}]", removedReportsData.getReport().getReportCode(),
                    removedReportsData.getReport().getEndGenerationDate(), removedReportsData.getReport().getFileName());
        }
        log.info("{} entrées supprimée(s) dans la BDD", removed.size());

        // Delete all files anterior to the deadline
        log.info("Répertoire des rapports classiques [" + REPORT_OUTPUT_FOLDER + "] ");
        List<String> deletedReports = deleteFilesBefore(REPORT_OUTPUT_FOLDER, deadlineReports);
        log.info("{} fichier(s) ont été supprimés:\n", deletedReports.size());
        log.info(StringHelper.implode("\n + ", deletedReports.toArray(new String[deletedReports.size()])));
    }

    private void purgeScheduledReportsBefore() {
        Calendar deadlineSchedulings = DateHelper.offsetDays(-SCHEDULING_REPORT_NB_DAYS);
        log.info("Nombre de jours définis pour les rapports planifies [{}]", SCHEDULING_REPORT_NB_DAYS);
        log.info("Démarrage de la purge des rapports planifies terminés avant le {}", new SimpleDateFormat().format(deadlineSchedulings.getTime()));
        log.info("Répertoire d'archivage des rapports planifiés [{}]", SCHEDULING_REPORT_OUTPUT_FOLDER_HISTORY);

        log.info("Suppression des entrées planifiées dans la base de donnée");
        List<RemovedReportEntry> removed = new MngReport().deleteScheduledReportsBeforeDate(deadlineSchedulings);
        for (RemovedReportEntry removedReportsData : removed) {
            log.info("\tTrouvé {} datant du [{}], nom du fichier[{}] (planifié)", removedReportsData.getReport().getReportCode(),
                    removedReportsData.getReport().getEndGenerationDate(), removedReportsData.getReport().getFileName());
        }
        log.info("{} entrées planifiéessupprimée(s) dans la BDD", removed.size());

        // List files
        List<File> toBeDeletedFromHistory = listSchedulingFiles(deadlineSchedulings);
        // Delete files
        FileHelper.deleteFiles(toBeDeletedFromHistory);
        log.info("{} fichier(s) ont été supprimés de l'historique:\n", toBeDeletedFromHistory.size());
        for (File file : toBeDeletedFromHistory) {
            log.info(" + {}", file.getName());
        }
    }

    private List<File> listSchedulingFiles(Calendar deadlineSchedulings) {
        List<File> toBeDeletedFromHistory = FileHelper.listFilesBefore(SCHEDULING_REPORT_OUTPUT_FOLDER_HISTORY, deadlineSchedulings);
        if (toBeDeletedFromHistory.isEmpty()) {
            log.info("\tAucun fichier de rapport planifié n'est candidat pour la suppression avant la date [{}]", deadlineSchedulings);
            return new ArrayList<>();
        }
        return toBeDeletedFromHistory;
    }

    /**
     * Deletes all the files in a folder that have been modified before the provided date.
     * 
     * @param folder the folder
     * @param deadlineReports the deadline
     * @return a list of all the deleted files' names
     */
    private static List<String> deleteFilesBefore(final File folder, final Calendar deadlineReports) {
        SimpleDateFormat sdf = new SimpleDateFormat(FORMAT_DATE);
        List<String> deletedFileNames = new ArrayList<>();
        for (File file : folder.listFiles()) {
            if (!file.isFile()) {
                continue; // Looking for a file, not a folder
            }
            String fileName = file.getName();
            Calendar fileMod = Calendar.getInstance();
            long lastModified = file.lastModified();
            fileMod.setTime(new Date(lastModified));
            if (fileMod.after(deadlineReports)) {
                continue; // looking for older files, from BEFORE the deadline
            }
            if (file.delete()) {
                log.info("Le fichier [{}] modifié le [{}] a été supprimé ", fileName, sdf.format(lastModified));
                deletedFileNames.add(fileName);
            } else {
                log.warn("Le fichier [{}] modifié le [{}] n'a pas pu être supprimé ", fileName, sdf.format(lastModified));
            }
        }
        return deletedFileNames;
    }

    /**
     * Build a job detail for Purge job.
     * 
     * @return jobDetail
     */
    public static JobDetail getPurgeJobDetail() {
        JobKey purgeKey = getPurgeJobKey();
        return JobBuilder.newJob(PurgeJob.class).withIdentity(purgeKey).withDescription(HUMAN_DESCRIPTION_FOR_PURGE).requestRecovery(FAIL_ON_RECOVERY)
                .storeDurably(true).build();
    }

    /**
     * Build a job trigger for Purge job.
     * 
     * @param jobDetail the requested job
     * 
     * @return trigger
     */
    public static Trigger getScheduledTrigger(final JobDetail jobDetail) {
        TriggerKey purgeTriggerKey = getPurgeTriggerKey();
        return TriggerBuilder.newTrigger().withIdentity(purgeTriggerKey).forJob(jobDetail).withSchedule(CRON_SCHEDULE).build();
    }

    /**
     * Build a job detail for Purge job.
     * 
     * @return jobDetail
     */
    public static JobKey getPurgeJobKey() {
        return new JobKey(JOB_NAME, JOB_GROUP_NAME);
    }

    /**
     * Build a job detail for Purge job.
     * 
     * @return jobDetail
     */
    private static TriggerKey getPurgeTriggerKey() {
        return new TriggerKey(JOB_NAME, JOB_GROUP_NAME);
    }

}
