package com.capgemini.reports.definition.r22report;

import com.capgemini.framework.common.FileHelper;
import com.capgemini.framework.common.SettingsHelper;
import com.capgemini.framework.common.StringHelper;
import com.capgemini.framework.common.TeamcenterDateFormatter;
import com.capgemini.framework.teamcenter.TeamcenterObjectTypes;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.*;
import com.capgemini.framework.teamcenter.files.FileManagement;
import com.capgemini.framework.teamcenter.files.FileManagement.FileType;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.framework.teamcenter.policies.PolicyLocation;
import com.capgemini.framework.teamcenter.queries.saved.modelobject.IMANRelationQuery;
import com.capgemini.framework.teamcenter.queries.saved.modelobject.ItemQuery;
import com.capgemini.framework.teamcenter.statuating.StatusSetter;
import com.capgemini.reports.JasperTemplate;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.ReportLaunchTypeEnum;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.builder.R22Builder;
import com.capgemini.reports.builder.ReportBuilder;
import com.capgemini.reports.database.javabean.DbReport;
import com.capgemini.reports.database.javabean.enumeration.ReportCriteriaEnum;
import com.capgemini.reports.database.manager.MngCriteriaVal.CriteriaValue;
import com.capgemini.reports.definition.r22report.ExecutionR22.ReportDataR22;
import com.capgemini.reports.exceptions.InvalidDatasetException;
import com.capgemini.reports.exceptions.NotFoundException;
import com.capgemini.reports.execution.AbstractExecutionReport;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.execution.actions.AbstractPostExecutionStep;
import com.capgemini.reports.execution.actions.IPostExecutionStep;
import com.capgemini.reports.printing.Printer.PrintFormat;
import com.capgemini.reports.reports.parameters.EffectivityDate;
import com.capgemini.reports.reports.parameters.IChecker;
import com.capgemini.reports.reports.parameters.MonoMulti;
import com.capgemini.reports.reports.parameters.checker.ObjectTypeChecker;
import com.teamcenter.services.loose.core._2006_03.FileManagement.GetDatasetWriteTicketsInputData;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.ImanRelation;
import com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.WorkspaceObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * This class controls the execution of an R22 report.
 */
public class ExecutionR22 extends AbstractExecutionReport<ExecutionR22, ReportDataR22> {
    /**
     * The policy to use if the report is launched by workflow, when attaching it to the part as a dataset.
     */
    public static final PolicyLocation R22_POLICY_XML_WKF = PolicyLocation.make("R22_policy_wkf", "/xml/policies/r22report/R22_policy_wkf.xml");
    private static final long serialVersionUID = -1050126556474290283L;
    private static final Logger log = LoggerFactory.getLogger(ExecutionR22.class);
    private static final int MILLIS_PER_SEC = 1000;
    // Global parameters
    private static final PolicyLocation R22_POLICY_XML_FULL = PolicyLocation.make("R22_policy_full", "/xml/policies/r22report/R22_policy_full.xml");
    private static final PolicyLocation R22_POLICY_XML_LIGHT = PolicyLocation.make("R22_policy_light", "/xml/policies/r22report/R22_policy_light.xml");
    private static final String OUTPUT_FOLDER = SettingsHelper.getReportLocation();
    private static final int REPORT_PRIORITY = SettingsHelper.getPriority(ReportEnum.REPORT_R22);
    private static final String REPORT_REVISION_RULE = SettingsHelper.getPropertyWithDefaultValue("common.bom.revisionRule.REPORT_R22", DEFAULT_REVISION_RULE);
    private static final ReportLaunchTypeEnum REPORT_LAUNCH_TYPE = ReportLaunchTypeEnum.getReportLaunchType("common.launch.launchType.REPORT_R22");

    // Constants
    private static final String FILE_SEPARATOR = "_";
    private static final String PATTERN_DATE = "dd-MM-yyyy";
    private static final String BOM_PAR_DATE = "BOMParDate";

    // Report Configuration
    private final EffectivityDate effectivity;
    private final String partId;
    private final String partRevision;
    private final MonoMulti monoMulti;


    /**
     * Executes the Report R22.
     *
     * @param partId       String - the Part Id
     * @param partRevision String - the Part Revision
     */
    public ExecutionR22(final String partId, final String partRevision) {
        super(REPORT_PRIORITY, REPORT_REVISION_RULE, REPORT_LAUNCH_TYPE);
        this.partId = partId;
        this.partRevision = partRevision;
        this.effectivity = new EffectivityDate(); // Always today
        this.monoMulti = MonoMulti.MULTI;
    }

    @Override
    public List<CriteriaValue> getLaunchData() {
        List<CriteriaValue> criteriaValues = new ArrayList<>();
        criteriaValues.add(new CriteriaValue(ReportCriteriaEnum.PART_ID, partId));
        criteriaValues.add(new CriteriaValue(ReportCriteriaEnum.PART_REVISION, partRevision));
        criteriaValues.add(new CriteriaValue(ReportCriteriaEnum.MONO_MULTI, monoMulti.toString()));
        return criteriaValues;
    }

    @Override
    public String getTemplateLocation() {
        return JasperTemplate.TEMPLATE_R22.getFilepath();
    }

    @Override
    public List<IChecker> getQuickChecks() {
        List<IChecker> quickChecks = new ArrayList<>();
        quickChecks.add(new ObjectTypeChecker(partId, SnecmaObjectTypes.ALTERNATE, SnecmaObjectTypes.DET, SnecmaObjectTypes.PART_AERO,
                SnecmaObjectTypes.PART_ASSEMB, SnecmaObjectTypes.PART_MAT, SnecmaObjectTypes.PART_NO_AERO, SnecmaObjectTypes.STANDARD));
        return quickChecks;
    }

    @Override
    public PolicyLocation getPropertyPolicyForLongChecks() {
        return R22_POLICY_XML_LIGHT;
    }

    @Override
    public PolicyLocation getFullPolicy() {
        return R22_POLICY_XML_FULL;
    }

    @Override
    public List<IChecker> getLongChecks() {
        return new ArrayList<>(); // Empty lists - not used for this report
    }

    @Override
    public ReportEnum getReportIdentifier() {
        return wkf ? ReportEnum.REPORT_W22 : ReportEnum.REPORT_R22;
    }

    @Override
    public String getSpecifiedFileName() {
        // Format file expected : R22-BOMParDATE_{ReferenceArticle}_{RevisionArticle}_{date_systeme}.pdf
        String dateSystem = TeamcenterDateFormatter.formatCalendarToPattern(Calendar.getInstance(), PATTERN_DATE);
        return "R22-BOMParDATE_" + partId + FILE_SEPARATOR + partRevision + FILE_SEPARATOR +
                dateSystem;
    }

    @Override
    public PrintFormat getPrintingFormat() {
        return PrintFormat.PDF;
    }

    public String getPartId() {
        return partId;
    }

    public String getPartRevision() {
        return partRevision;
    }

    public EffectivityDate getEffectivity() {
        return effectivity;
    }

    @Override
    public IDataReportPreparator<ExecutionR22> getDataPreparator() {
        return new DataReportPreparatorR22();
    }

    @Override
    public AbstractExecutionReport<?, ?> cloneTemplateToExecution() {
        return new ExecutionR22(partId, partRevision);
    }

    /**
     * Returns a {@link IPostExecutionStep} which attaches the report to the Part as a Dataset (and feezes the Dataset).
     *
     * @return the action
     */
    public IPostExecutionStep makeAttachPdfToPartAction() {
        return new R22AttachPdfToPartAction();
    }


    /* ************************************************************************************
     *  Workflow action
     * ************************************************************************************ */

    private File copyFile(final String datasetFileName) throws NotFoundException {
        String originalFileNameOnDisc = getFileNameOnDisc();
        File original = new File(OUTPUT_FOLDER + originalFileNameOnDisc + ".pdf");
        final File copy = new File(OUTPUT_FOLDER + datasetFileName + ".pdf");
        FileHelper.copy(original, copy);
        return copy;
    }

    private String makeDatasetName() {
        return StringHelper.implode(FILE_SEPARATOR, BOM_PAR_DATE, partId, partRevision);
    }

    private String makeDatasetFileName() {
        Date startDate;
        try {
            startDate = new SimpleDateFormat(TeamcenterDateFormatter.DD_MM_YYYY_HH_MM_SS).parse(getStartTime());
        } catch (ParseException e) {
            log.warn("Unable to parse stored start date, generating a date using NOW", e);
            startDate = new Date();
        }
        String dateStr = new SimpleDateFormat(TeamcenterDateFormatter.YYYYMMDD).format(startDate);
        long timestamp = startDate.getTime() / MILLIS_PER_SEC;
        String timestampStr = String.valueOf(timestamp);
        return StringHelper.implode(FILE_SEPARATOR, BOM_PAR_DATE, partId, partRevision, dateStr, timestampStr);
    }

    private Item obtainPart(final TcSession session) throws NotFoundException {
        List<Item> matchingParts = new ItemQuery(session).searchID(partId).findAll();

        for(Item currentPart : matchingParts){
            if(SnecmaObjectTypes.isPart(currentPart.getTypeObject().getName())){
                return currentPart;
            }
        }

        if (matchingParts.isEmpty()) {
            throw new NotFoundException("Could not find the original part " + partId);
        } else {
            throw new NotFoundException(partId + " part not found other type found instead");
        }

    }

    private void destroyConflictingDatasets(final TcSession session, final String datasetName, final Item thePart)
            throws FailedRelationshipActionException {
        List<ImanRelation> existingRelations = new IMANRelationQuery(session).find(partId, datasetName);
        log.debug("Wokflow launch: destrying previous datasets: {} relations found", existingRelations.size());
        // Let's browse all conflicting datasets
        for (ImanRelation relation : existingRelations) {
            ModelObject datasetMO = relation.get_secondary_object();

            // Let's destroy the relation first
            log.debug("Wokflow launch: destroying relation between part {} and datasets {}", thePart.getUid(), datasetMO.getUid());
            session.deleteObjects(relation);

            // Net's destroy the Dataset now that it is free from relationships
            log.debug("Wokflow launch: destroying datasets {}", datasetMO.getUid());
            session.deleteObjects(datasetMO);
        }
    }

    private GetDatasetWriteTicketsInputData createDataset(final TcSession session, final String datasetName, final File fileCopy)
            throws InvalidDatasetException {
        String dataSetDescription = ReportEnum.REPORT_R22.getReportTitle();

        // Perform the upload
        GetDatasetWriteTicketsInputData data;
        try {
            data = FileManagement.addSingleDataSet(session, datasetName, dataSetDescription, fileCopy, FileType.FICHE_ARTICLE);
        } catch (MalformedDataSetException | ConnectionMisconguredException e) {
            throw new InvalidDatasetException("Unable to create the dataset for R22 report " + getReportTechnicalUid(), e);
        }
        return data;
    }

    private void linkDatasetToPart(final TcSession session, final Item thePart, final ModelObject dataset)
            throws FailedRelationshipActionException {
        try {
            session.createRelation(thePart, dataset, TeamcenterObjectTypes.IMAN_REFERENCE);
        } catch (FailedRelationshipActionException e) {
            // Unable to link back the dataset to the part. Let's cleanup behind us and destroy that orphan dataset!
            session.deleteObjects(dataset);
            throw e;
        }

    }

    private void addFrozenStatusToDataset(final TcSession session, final ModelObject dataset) throws UnableToCreateStatusException {
        StatusSetter statusMng = new StatusSetter(session);
        statusMng.putStatus((WorkspaceObject) dataset, StatusEnum.FROZEN.getIdentifier());
    }

    @Override
    public Class<? extends ReportBuilder> getReportBuilderClass() {
        return R22Builder.class;
    }

    /**
     * POJO container of all data required for this report.
     */
    public static class ReportDataR22 {
        private final String clausePropriete;
        private final BOMLineHolder topLine;

        protected ReportDataR22(final String clausePropriete, final BOMLineHolder topLine) {
            this.clausePropriete = clausePropriete;
            this.topLine = topLine;
        }

        public String getClausePropriete() {
            return clausePropriete;
        }

        public BOMLineHolder getTopLine() {
            return topLine;
        }
    }

    private class R22AttachPdfToPartAction extends AbstractPostExecutionStep<ExecutionR22, Void> {
        private static final long serialVersionUID = -1516821856454786852L;

        /**
         * Creates an action that will attach the pdf to the part.
         */
        private R22AttachPdfToPartAction() {
            super(ExecutionR22.class);
        }

        @Override
        public Void performPostActionOnReport(final TcSession session, final ExecutionR22 report, final DbReport theExecutingReport,
                                              final String fileNameWithExtension) throws KurtException {
            session.setObjectPropPolicy(R22_POLICY_XML_WKF);

            // Prepare naming & Make a copy with the correct name
            log.debug("Wokflow launch: Beginning file copy");
            String datasetName = report.makeDatasetName();
            String datasetFileName = report.makeDatasetFileName();
            File fileCopy = report.copyFile(datasetFileName);

            // Obtain the Part
            Item thePart = report.obtainPart(session);

            // Detect & destroy conflicting datasets on the part, if any
            log.debug("Wokflow launch: Destroying previous datasets...");
            report.destroyConflictingDatasets(session, datasetName, thePart);

            // Create a new dataset
            log.debug("Wokflow launch: Creating datasets {}", datasetName);
            GetDatasetWriteTicketsInputData dataset = report.createDataset(session, datasetName, fileCopy);

            // Link the part to the dataset (if it fails, it will cleanup the dataset as well)
            log.debug("Wokflow launch: Linking  Part {} to newly created dataset {}", thePart.getUid(), dataset.dataset.getUid());
            report.linkDatasetToPart(session, thePart, dataset.dataset);

            // Freeze the dataset
            log.debug("Wokflow launch: Adding 'Frozen' status to the dataset {}", dataset.dataset.getUid());
            report.addFrozenStatusToDataset(session, dataset.dataset);

            log.debug("R22 Execution from workflow has completed successfuly for part {}-{}", partId, partRevision);
            return null;
        }
    }
}
