package com.capgemini.reports.changeobjects;

import com.capgemini.framework.common.StringHelper;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum;
import com.capgemini.framework.teamcenter.object.pom.status.TcReleaseStatus;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.reports.changeobjects.change.TcChangeRequestRevision;
import com.capgemini.reports.documentationobjects.DocTechnic;
import com.capgemini.reports.snecmaobjects.generic.AbstractGeneric;
import com.capgemini.reports.snecmaobjects.generic.Generic;
import com.capgemini.reports.snecmaobjects.generic.GenericAssemb;
import com.capgemini.reports.snecmaobjects.product.ProductRevision;
import com.capgemini.reports.teamcenter.queries.saved.WorkflowOnUnitQuery;
import com.capgemini.reports.workflows.Task;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.*;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

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

import static com.capgemini.framework.teamcenter.object.SnecmaObjectProperties.*;
import static com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes.*;

/**
 * Class representing a Unit.
 */
@Slf4j
@EqualsAndHashCode
public class UnitRevision implements Serializable {
    private static final long serialVersionUID = -1152481045274400434L;

    private static final String REGEX_DOCUMENT_DE_GEL_OR_FREEZE_DOCUMENT = "(.*\\bDocument de gel\\b.*)|(.*\\bFreeze document\\b.*)";
    private static final String TYPE_ITEM_REVISION = "ItemRevision";
    private static final String EMPTY_STRING = StringHelper.EMPTY;

    // Messages
    private static final String MSG_NO_RELEASE_STATUS_FOUND = "No ReleaseStatus has been found on the unit revision {}";
    private static final String MSG_NO_GENERIC_FOUND = "No Generic has been found on the Unit revision {}";

    private final TcSession session;
    private final ItemRevision itemRevision;

    private UnitRevision(final TcSession session, final ItemRevision itemRevision) {
        this.session = session;
        this.itemRevision = itemRevision;
    }


    /**
     * Build a UnitRevision. <br>
     */
    public static UnitRevision build(final TcSession session, final ItemRevision unitTc) {
        return new UnitRevision(session, unitTc);
    }

    /**
     * Choose an appropriate revision of Unit in a list of units ({@link UnitRevision}) from a reference range.<br/>
     * This list may be the list of built {@link UnitRevision} in a built {@link ProductRevision}. <br>
     *
     * @param referenceRange    String - the wanted range
     * @param unitListinProduct List<UnitRevision> - the list of {@link UnitRevision}
     * @param wantedStatus      {@link StatusEnum} - all the wanted status
     * @return the selected UnitRevision
     */
    public static UnitRevision chooseRevisionOfUnitFromRange(final String referenceRange, final List<UnitRevision> unitListinProduct,
                                                             final StatusEnum... wantedStatus) {
        // Obtain the list of all the UnitRevision with same range
        List<UnitRevision> resultUnits = UnitRevision.getUnitRevisionWithSameRange(referenceRange, unitListinProduct);
        if (resultUnits.size() == 1) {
            return resultUnits.get(0);
        }

        // Select A unit
        return UnitRevision.getUnitRevisionFromStatusWithMostRecentReleaseDate(resultUnits, null, wantedStatus);
    }

    /**
     * Return all the {@link UnitRevision} with the same range as the reference parameter.
     *
     * @param referenceRange String - the reference range
     * @param unitList       List<UnitRevision> - the list of unit
     * @return the list of UnitRevision with the same range
     */
    private static List<UnitRevision> getUnitRevisionWithSameRange(final String referenceRange, final List<UnitRevision> unitList) {
        List<UnitRevision> selectedUnits = new ArrayList<>();
        for (UnitRevision unitRev : unitList) {
            if (referenceRange.equals(unitRev.getUnitRange())) {
                selectedUnits.add(unitRev);
            }
        }
        return selectedUnits;
    }

    /**
     * Return all the {@link ItemRevision} with the same range as the reference parameter.
     *
     * @param referenceRange String - the reference range
     * @param unitList       List<ItemRevision> - the list of unit
     * @return the list of ItemRevision with the same range
     */
    public static List<ItemRevision> getItemRevisionWithSameRange(final TcSession session, final String referenceRange, final List<ItemRevision> unitList) {
        List<ItemRevision> selectedUnits = new ArrayList<>();
        for (ItemRevision unitRev : unitList) {
            if (referenceRange.equals(getRangeOfItemRevision(session, unitRev))) {
                selectedUnits.add(unitRev);
            }
        }
        return selectedUnits;
    }

    /**
     * Get the range of a Unit ({@link ItemRevision}).
     *
     * @param unit {@link ItemRevision} - the entry unit
     * @return unitRange String - the range of the unit. May be null.
     */
    public static String getRangeOfItemRevision(final TcSession session, final ItemRevision unit) {
        session.loadProperties(unit, "release_status_list");
        ReleaseStatus[] releaseStatuses = unit.get_release_status_list();
        session.loadProperties(releaseStatuses);
        for (ReleaseStatus releaseStatus : releaseStatuses) {
            if (SnecmaObjectTypes.EFFECTIVITY.equals(releaseStatus.get_name())) {
                session.loadProperties(releaseStatus, "effectivities");
                ModelObject[] effectivities = releaseStatus.get_effectivities();
                session.loadProperties(effectivities);
                if (effectivities.length == 0) {
                    log.error("ReleaseStatus named 'TcEffectivity' does not contain Effectivities...");
                } else {
                    ModelObject objectEffectivity = effectivities[0]; // There should only be one effectivity, we ignore the rest of the list's content
                    // Cast of the ModelObject to the TcEffectivity of Teamcenter, do not confuse with the TcEffectivity of this application
                    return ((Effectivity) objectEffectivity).get_unit_range_text();
                }
            }
        }
        return null;
    }

    /**
     * Returns the {@link UnitRevision} whose {@link TcReleaseStatus} has the most recent release date, amongst the provided UnitRevision.<br>
     *
     * @param unitRevisions the List of candidate {@link UnitRevision}s
     * @param userDate      the limit user date, only items released before that date will be examined
     * @param wantedStatus  {@link StatusEnum} - Enumeration of wanted status
     * @return the most up-to-date UnitRevision
     */
    private static UnitRevision getUnitRevisionFromStatusWithMostRecentReleaseDate(final List<UnitRevision> unitRevisions, final Calendar userDate,
                                                                                   final StatusEnum... wantedStatus) {
        Calendar latestReleaseDate = null;
        UnitRevision highestUnitRevision = null;
        for (UnitRevision curUnitRevision : unitRevisions) {
            // Get the TcReleaseStatus
            List<TcReleaseStatus> listReleaseStatus;
            if (wantedStatus == null || wantedStatus.length == 0) {
                listReleaseStatus = curUnitRevision.getReleaseStatusContainingMaturity();
            } else {
                listReleaseStatus = TcReleaseStatus.filter(curUnitRevision.getReleaseStatusContainingMaturity(), wantedStatus);
            }

            // Select the most recent
            for (TcReleaseStatus status : listReleaseStatus) {
                if (status != null) {
                  Calendar date = status.dateReleased();
                    if (latestReleaseDate == null && safeBefore(userDate, date)) {
                        highestUnitRevision = curUnitRevision;
                        latestReleaseDate = date;
                    } else if (latestReleaseDate != null && latestReleaseDate.compareTo(date) < 0 && safeBefore(userDate, date)) {
                        highestUnitRevision = curUnitRevision;
                        latestReleaseDate = date;
                    }
                }
            }
        }
        return highestUnitRevision;
    }

    private static boolean safeBefore(final Calendar userDate, final Calendar date) {
        return userDate == null || date.before(userDate);
    }

    public String getUid() {
        return itemRevision.getUid();
    }

    public String getItemId() {
        return itemRevision.get_item_id();
    }

    public String getRevision() {
        return itemRevision.get_item_revision_id();
    }

    public String getName() {
        return itemRevision.get_object_name();
    }

    public String getType() {
        return itemRevision.getTypeObject().getName();
    }

    public boolean isExceptionnel() {
        String unitExceptionnel = getMasterFormRev().getPropertyObject(PV_UNIT_EXCEPTIONNEL).getStringValue();
        return "Oui / Yes".equalsIgnoreCase(unitExceptionnel);
    }

    public DocTechnic getTechnicalDocGel() {
        session.loadProperties(itemRevision, PV_DELIVERABLES);
        ModelObject[] documents = itemRevision.getPropertyObject(PV_DELIVERABLES).getModelObjectArrayValue();
        session.loadProperties(documents);
        Calendar latestMod = null;
        DocTechnic technicalDocGel = null;
        for (ModelObject currentDoc : documents) {
            ItemRevision docAsItemRev = (ItemRevision) currentDoc;
            if (DOC_TECHNIC_REVISION.equals(docAsItemRev.getTypeObject().getName())) {
                DocTechnic technicalDoc = new DocTechnic(session, docAsItemRev);
                if (technicalDoc.getTypeDocTechnic1().matches(REGEX_DOCUMENT_DE_GEL_OR_FREEZE_DOCUMENT)) {
                    Calendar modDate = docAsItemRev.get_last_mod_date();
                    if (latestMod == null || modDate.after(latestMod)) {
                        latestMod = modDate;
                        technicalDocGel = technicalDoc;
                    }
                }
            }
        }
        return technicalDocGel;
    }

    private Form getMasterFormRev() {
        Form masterTag = itemRevision.get_item_master_tag();
        session.loadProperties(masterTag);
        return masterTag;
    }

    /**
     * Returns the Unit Range of a built UnitRevision.<br>
     * Obtained from the TcReleaseStatus named "TcEffectivity", who owns a (single) TcEffectivity, who owns the requested range.
     *
     * @return the unit Range
     */
    public String getUnitRange() {
        TcReleaseStatus tcReleaseStatuses = getReleaseStatusContaininEffectivity();
        if (tcReleaseStatuses == null || tcReleaseStatuses.getEffectivity() == null) {
            return EMPTY_STRING;
        } else {
            return tcReleaseStatuses.getEffectivity().unitRange();
        }
    }


    public List<TcReleaseStatus> getReleaseStatusContainingMaturity() {
        List<TcReleaseStatus> tcReleaseStatuses = new ArrayList<>();
        session.loadProperties(itemRevision, "release_status_list");
        ReleaseStatus[] releaseStatuses = itemRevision.get_release_status_list();
        session.loadProperties(releaseStatuses);
        if (releaseStatuses.length == 0) {
            log.debug(MSG_NO_RELEASE_STATUS_FOUND, itemRevision.get_item_id());
        } else {
            for (ReleaseStatus status : releaseStatuses) {
                TcReleaseStatus statusSnecma = new TcReleaseStatus(session, status);
                if (!statusSnecma.isEffectivity()) {
                    tcReleaseStatuses.add(statusSnecma);
                }
            }
        }
        return tcReleaseStatuses;
    }

    public TcReleaseStatus getReleaseStatusContaininEffectivity() {
        session.loadProperties(itemRevision, "release_status_list");
        ReleaseStatus[] releaseStatuses = itemRevision.get_release_status_list();
        session.loadProperties(releaseStatuses);
        if (releaseStatuses.length == 0) {
            log.debug(MSG_NO_RELEASE_STATUS_FOUND, itemRevision.get_item_id());
        } else {
            for (ReleaseStatus status : releaseStatuses) {
                TcReleaseStatus statusSnecma = new TcReleaseStatus(session, status);
                if (statusSnecma.isEffectivity()) {
                    return statusSnecma;
                }
            }
        }
        return null;
    }


    public List<AbstractGeneric> getGenerics() {
        List<AbstractGeneric> tcGenerics = new ArrayList<>();
        session.loadProperties(itemRevision, PV_GENERIC);
        ModelObject[] generics = itemRevision.getPropertyObject(PV_GENERIC).getModelObjectArrayValue();
        session.loadProperties(generics);
        if (generics.length == 0) {
            log.debug(MSG_NO_GENERIC_FOUND, itemRevision.get_item_id());
        } else {
            for (ModelObject generic : generics) {
                String objectType = generic.getTypeObject().getName();
                if (GENERIC.equals(objectType)) {
                    tcGenerics.add(new Generic(session, (Item) generic));
                } else if (GENERIC_ASSEMB.equals(objectType)) {
                    tcGenerics.add(new GenericAssemb(session, (Item) generic));
                } else {
                    log.warn("Obtained an objectType in the pseudofolder Generic of a Unit, which is not a Generic or Generic Assemb");
                }
            }
        }

        return tcGenerics;
    }

    public List<TcChangeRequestRevision> getChangeRequestsList() {
        List<TcChangeRequestRevision> changeRequestRevs = new ArrayList<>();
        session.loadProperties(itemRevision, PV_CHANGE_LIST);
        ModelObject[] changes = itemRevision.getPropertyObject(PV_CHANGE_LIST).getModelObjectArrayValue();
        session.loadProperties(changes);
        for (ModelObject currentChange : changes) {
            ItemRevision changeAsItemRev = (ItemRevision) currentChange;
            String changeType = changeAsItemRev.getTypeObject().getName();
            if (changeType != null && changeType.contains("PV4")) { // Filter the changes: we only want the new types of objects, whose names begin with 'PV4'
                changeRequestRevs.add(new TcChangeRequestRevision(session, changeAsItemRev));
            }
        }

        return changeRequestRevs;
    }


    public List<Task> getWorkflowsValidationUnitParSO() {
        List<Task> tasks = new ArrayList<>();
        List<EPMTask> result = WorkflowOnUnitQuery.searchValidationUnitParSO(session, getItemId()).findAll();
        for (EPMTask process : result) {
            tasks.add(new Task(session, process));
        }
        return tasks;
    }

    @Override
    public String toString() {
        return getType() + " with ID= " + getItemId() + " REV= " + getRevision() + " RANGE= " + getUnitRange();
    }
}
