package com.capgemini.reports.definition.r15r16r17reports.r15r17reports;

import com.capgemini.framework.common.LanguageHelper;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum.AbstractStatusFilter;
import com.capgemini.framework.teamcenter.object.pom.status.TcReleaseStatus;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.reports.definition.r15r16r17reports.AbstractDataReportPreparatorR15R16R17;
import com.capgemini.reports.documentationobjects.DocDrawing;
import com.capgemini.reports.documentationobjects.DocGeometry;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.NotSupportedException;
import com.capgemini.reports.exceptions.UserInputErrorEnum;
import com.capgemini.reports.snecmaobjects.SnecmaObjectFactory;
import com.capgemini.reports.snecmaobjects.part.AbstractPart;
import com.capgemini.reports.teamcenter.ObjectInfoHelper;
import com.capgemini.reports.teamcenter.ObjectInfoLoader;
import com.capgemini.reports.teamcenter.queries.saved.ChangeFromPartQuery;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import com.teamcenter.soa.client.model.strong.ReleaseStatus;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static com.capgemini.framework.teamcenter.object.SnecmaObjectProperties.PV4_TYPE_MODIFICATION;

/**
 * Abstract Class whose goal is to prepare common data to R15 R17 reports.
 *
 * @param <T> the report {@link AbstractExecutionR15R17}
 */
@Slf4j
@EqualsAndHashCode(callSuper = true)
public abstract class AbstractDataReportPreparatorR15R17<T extends AbstractExecutionR15R17<T>> extends AbstractDataReportPreparatorR15R16R17<T> {
    private static final long serialVersionUID = -7130318844369564083L;

    private static final AbstractStatusFilter APPROVED_STATUS = StatusEnum.allow(StatusEnum.DEF_APPROVED, StatusEnum.PRELIM_DEF_APPROVED);
    private static final AbstractStatusFilter CANCELLED_STATUS = StatusEnum.allow(StatusEnum.CANCELLED);
    private static final String REVISION_ENG_CHANGE = SnecmaObjectTypes.REVISION_ENG_CHANGE;
    private static final String DEFINITION_EN_COURS = "définition en cours";
    private static final String DEFINITION_ANNULEE = "définition annulée";
    private static final String WHITESPACE = " ";

    /**
     * Find the top ItemRevision in the list of all the revisions.
     *
     * @param wantedRevision        {@link String} - the wanted revision
     * @param revisionsSnecmaObject List<{@link ItemRevision}> - the list of all the revisions of the top object
     * @return the selected ItemRevision
     */
    protected ItemRevision retrieveTopItemRevision(final String wantedRevision, final List<ItemRevision> revisionsSnecmaObject) {
        ItemRevision topItemRevision = null;
        for (ItemRevision itemRev : revisionsSnecmaObject) {
            if (itemRev == null || !SnecmaObjectTypes.isPartRevision(itemRev.getTypeObject().getName())) {
                continue;
            }
            String revision = itemRev.get_item_revision_id();
            if (revision != null && revision.equals(wantedRevision)) {
                topItemRevision = itemRev;
                break;
            }
        }
        return topItemRevision;
    }

    /**
     * Prepare all common data between report 15 , report 16 and report 17.
     *
     * @param session               {@link TcSession} - the tc session
     * @param topItemRevision       {@link ItemRevision} - the top object
     * @param revisionsSnecmaObject List<{@link ItemRevision}> - the list of all the revisions of the top object
     * @param report                - the execution report which provide all informations for the report
     * @throws KurtException             if anything else goes wrong
     * @throws InvalidUserInputException the user-provided top item doesn't seem to exist
     */
    protected void doPrepareCommonDataForAllReports(final TcSession session, final ItemRevision topItemRevision,
                                                    final List<ItemRevision> revisionsSnecmaObject, final T report)
            throws KurtException {
        // Verify the object type: any Part but not an Alternate
        checkAndBuildSnecmaObject(session, topItemRevision, report);

        report.setTypeProduit(ObjectInfoLoader.getProductType(session, topItemRevision));
        report.setClausePropriete(ObjectInfoLoader.getClausePropriete(report.getTypeProduit(), session));
        retrieveDateReleased(session, topItemRevision, report);

        retrieveDateLancementAndMT(revisionsSnecmaObject, report.itemRevision, session, report);
        retrieveRevisionArticle(session, report);

        retrieveDocGeometryAndDocDrawingLists(session, topItemRevision, report);
        retrieveDocNormAndDocNormStandardLists(session, topItemRevision, report);
        retrieveDocTechnicList(session, topItemRevision, report);
    }

    /**
     * Check and build the SnecmaObject.
     */
    protected void checkAndBuildSnecmaObject(final TcSession session, final ItemRevision topItemRevision, final T report)
            throws InvalidUserInputException, NotSupportedException {
        String itemType = topItemRevision.getTypeObject().getName();
        if (!SnecmaObjectTypes.isA(itemType, SnecmaObjectTypes.PART_AERO_REVISION, SnecmaObjectTypes.PART_NO_AERO_REVISION,
                SnecmaObjectTypes.PART_ASSEMB_REVISION, SnecmaObjectTypes.PART_MAT_REVISION, SnecmaObjectTypes.DET_REVISION,
                SnecmaObjectTypes.STANDARD_REVISION)) {
            throw new InvalidUserInputException(UserInputErrorEnum.THE_SELECTED_OBJECT_IS_NOT_PERMITTED_ON_THIS_REPORT);
        }
        report.snecmaObject = SnecmaObjectFactory.build(session, topItemRevision);
    }

    /**
     * Retrieve the parameter RevisionArticle.
     */
    protected void retrieveRevisionArticle(final TcSession session, final T report) {
        // Find an associate Change with the Part
        // Maybe the change is a ChangeRequest Revision or a ChangeNotice Revision
        List<ItemRevision> selectedChanges = new ArrayList<>();
        if (report.mostRecentDateReleased != null) {
            selectedChanges = returnChangeItemWithSameDateReleased(report.mostRecentDateReleased, report.getItemId(), report.itemRevision, session,
                    report.statusName);
        }

        // Put revision article's parameter for the report
        if (selectedChanges.isEmpty() || selectedChanges.size() > 1) {
            report.setRevisionArticle(report.snecmaObject.getRevisionId());
        } else {
            ItemRevision selectedChange = selectedChanges.get(0);
            String typeModif = selectedChange.getPropertyObject(PV4_TYPE_MODIFICATION).getStringValue();
            report.setRevisionArticle(selectedChange.get_item_id() + WHITESPACE + (report.isFrench() ? LanguageHelper.french(typeModif) : LanguageHelper.english(typeModif)));
        }
    }

    /**
     * Return the ChangeItem (ChangeRequest or TcChangeNoticeRevision) with the same date released as the provided Calendar.
     *
     * @param referenceDate  {@link Calendar} - the reference date of the reference object
     * @param itemId         {@link String} - the id of the reference object
     * @param itemRevisionId {@link String} - the revision id of the reference object
     * @param session        {@link TcSession} - the TC session for query
     * @param statusName     {@link String} - the reference name of TcReleaseStatus of the reference object
     * @return the selected ChangeItem Revision
     */
    protected List<ItemRevision> returnChangeItemWithSameDateReleased(final Calendar referenceDate, final String itemId, final String itemRevisionId,
                                                                      final TcSession session, final String statusName) {
        // Query of all associated Change with the Part
        List<ItemRevision> listChangeRev = new ChangeFromPartQuery(session).searchIDAndRevision(itemId, itemRevisionId).findAll();

        // Select ONE change associated to the precedent selected ReleaseStatus of the date released of the Part
        List<ItemRevision> selectedChanges = new ArrayList<>();
        for (ItemRevision changeRev : listChangeRev) {
            if (changeRev == null) {
                continue;
            }

            // Filter the Revision EngChange
            String typeChange = changeRev.getTypeObject().getName();
            if (REVISION_ENG_CHANGE.equals(typeChange)) {
                continue;
            }

            // Compare the status name and the date released to the references
            boolean isValidated = validChange(session, changeRev, referenceDate, statusName);
            if (isValidated) {
                selectedChanges.add(changeRev);
            }
        }
        return selectedChanges;
    }

    private boolean validChange(final TcSession session, final ItemRevision changeRev, final Calendar referenceDate, final String statusName) {
        boolean isValidated = false;
        ReleaseStatus[] releaseStatusList = getReleaseStatusList(session, changeRev);
        for (ReleaseStatus status : releaseStatusList) {
            if (status == null) {
                continue;
            }

            Calendar changeCalendar = status.get_date_released();
            if (changeCalendar == null) {
                continue;
            }

            // Filter with the name of the reference ReleaseStatus
            if (!statusName.equals(status.get_name())) {
                break;
            }

            // Only ONE change can have the same ReleaseStatus with the same date released
            // Warning : Calendar.MONTH return 0 for January and 11 for December
            if (changeCalendar.get(Calendar.DAY_OF_MONTH) == referenceDate.get(Calendar.DAY_OF_MONTH)
                    && changeCalendar.get(Calendar.MONTH) == referenceDate.get(Calendar.MONTH)
                    && changeCalendar.get(Calendar.YEAR) == referenceDate.get(Calendar.YEAR)) {
                isValidated = true;
            }
        }
        return isValidated;
    }

    private void retrieveDocGeometryAndDocDrawingLists(final TcSession session, final ItemRevision partItemRevision, final T report) {
        List<ItemRevision> representedDocObjects = AbstractPart.returnRepresentedDocuments(session, partItemRevision);
        for (ItemRevision representedDoc : representedDocObjects) {
            if (representedDoc == null) {
                continue;
            }
            String type = representedDoc.getTypeObject().getName();
            if (SnecmaObjectTypes.DOC_GEOMETRY_REVISION.equals(type)) {
                DocGeometry docGeo = new DocGeometry(session, representedDoc);
                report.docGeometryList.add(docGeo);
            } else if (SnecmaObjectTypes.DOC_DRAWING_REVISION.equals(type)) {
                DocDrawing docDrawing = new DocDrawing(session, representedDoc);
                report.docDrawingList.add(docDrawing);
            }
        }
    }

    /**
     * Retrieve the date released of the report through the main itemRevision's properties.
     *
     * @param partItemRevision {@link ItemRevision} - the part object
     * @param report           - the execution report which provide all informations for the report
     */
    public void retrieveDateReleased(final TcSession session, final ItemRevision partItemRevision, final T report) {
        // Find the selected ReleaseStatus
        ReleaseStatus selectStatus = selectStatus(session, partItemRevision);
        if (selectStatus == null) {
            report.setDateReleased(DEFINITION_EN_COURS);
            log.warn("No ReleaseStatus were found on the {} {}.", partItemRevision.getTypeObject().getName(), partItemRevision.getUid());
            return;
        }

        // Retrieve the date released
        Calendar selectedDate = selectStatus.get_date_released();
        if (selectedDate == null) {
            report.setDateReleased(DEFINITION_EN_COURS);
            log.warn("Unable to retrieve the date released of the selected ReleaseStatus {} {}.", selectStatus.getTypeObject().getName(),
                    selectStatus.getUid());
            return;
        }

        // Retrieve informations about TcReleaseStatus in order to compare with revision article
        report.mostRecentDateReleased = selectedDate;
        report.statusName = selectStatus.get_name();

        // Put date released's appropriate value for report
        if (APPROVED_STATUS.isIncluded(report.statusName)) {
            report.setDateReleased(getDateFormatDayMonthYear(selectedDate));
        } else {
            if (CANCELLED_STATUS.isIncluded(report.statusName)) {
                report.setDateReleased(DEFINITION_ANNULEE);
            } else {
                report.setDateReleased(DEFINITION_EN_COURS);
            }
        }
    }

    private ReleaseStatus selectStatus(final TcSession session, final ItemRevision partItemRevision) {
        return ObjectInfoHelper.getStatusWithMostRecentReleaseDate(session, partItemRevision, StatusEnum.J3A, StatusEnum.J3B, StatusEnum.EFFECTIVITY);
    }

    /**
     * Retrieve the Date Lancement and the MT property.
     *
     * @param revisionsSnecmaObject List<{@link ItemRevision}> - the list of revisions of the top Part
     * @param revisionTopPart       {@link String} - the revision of the top Part
     * @param session               {@link TcSession} - the tc session
     * @param report                - the execution report which provide all informations for the report
     */
    public void retrieveDateLancementAndMT(final List<ItemRevision> revisionsSnecmaObject,
                                           final String revisionTopPart, final TcSession session, final T report) {
        // Date released of the part (may be the date system)
        Calendar dateRefFormatcalendar = selectCalendar(report);

        // Find the previous ItemRevision (may not be the direct predecessor) and the previous Date
        ItemRevision previousRev = null;
        Calendar selectedDate = null;
        for (ItemRevision itemRev : revisionsSnecmaObject) {
            if (itemRev == null) {
                continue;
            }
            String revision = itemRev.get_item_revision_id();
            if (revision == null) {
                continue;
            }
            if (revision.compareTo(revisionTopPart) < 0) {
                List<ReleaseStatus> listRsPrecedentRev = TcReleaseStatus.getChosenStatus(session, itemRev, StatusEnum.J3B);
                if (listRsPrecedentRev.isEmpty()) {
                    continue;
                }
                for (ReleaseStatus status : listRsPrecedentRev) {
                    if (status == null) {
                        continue;
                    }
                    Calendar date = status.get_date_released();
                    if (date != null && date.compareTo(dateRefFormatcalendar) < 0) {
                        if (selectedDate == null) {
                            previousRev = itemRev;
                            selectedDate = date;
                        } else if (selectedDate.compareTo(date) < 0) {
                            previousRev = itemRev;
                            selectedDate = date;
                        }
                    }
                }
            }
        }

        // Put MT and previousDateReleased data
        putPrecedentRevisionParameters(session, previousRev, selectedDate, report);
    }

    private void putPrecedentRevisionParameters(final TcSession session, final ItemRevision previousRev, final Calendar selectedDate,
                                                final T report) {
        if (previousRev == null) {
            report.varMt = "";
            report.previousReleaseDate = "";
        } else {
            // Previous date released parameter for report
            report.previousReleaseDate = getDateFormatDayMonthYear(selectedDate);

            // MT parameter for report
            String previousId = previousRev.get_item_id();
            String previousRevisionId = previousRev.get_item_revision_id();
            List<ItemRevision> selectedPrecedentChange =
                    returnChangeItemWithSameDateReleased(selectedDate, previousId, previousRevisionId, session, StatusEnum.J3B.getIdentifier());
            if (selectedPrecedentChange.isEmpty() || selectedPrecedentChange.size() > 1) {
                report.varMt = "";
            } else {
                report.varMt = selectedPrecedentChange.get(0).get_item_id();
            }
        }
    }

    private Calendar selectCalendar(final T report) {
        Calendar refCalendarForPrecedentRev;
        if (DEFINITION_EN_COURS.equals(report.getDateReleased()) || DEFINITION_ANNULEE.equals(report.getDateReleased())
                || (report.mostRecentDateReleased == null)) {
            refCalendarForPrecedentRev = Calendar.getInstance();
        } else {
            refCalendarForPrecedentRev = report.mostRecentDateReleased;
        }
        return refCalendarForPrecedentRev;
    }

    private ReleaseStatus[] getReleaseStatusList(final TcSession session, final ItemRevision itemRev) {
        session.loadProperties(itemRev, "release_status_list");
        ReleaseStatus[] releaseStatuses = itemRev.get_release_status_list();
        session.loadProperties(releaseStatuses);
        return releaseStatuses;
    }

    @Override
    public boolean isEmptyTable(final T report) {
        return super.isEmptyTable(report) && report.getDocDrawingList().isEmpty() && report.getDocGeometryList().isEmpty();
    }
}
