package com.capgemini.reports.definition.r20report.checker;

import com.capgemini.framework.teamcenter.authent.TcSession;
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.types.SnecmaObjectTypes;
import com.capgemini.reports.changeobjects.UnitRevision;
import com.capgemini.reports.definition.r20report.ExecutionR20;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.UserInputErrorEnum;
import com.capgemini.reports.releasestatus.RevisionStatusAndDate;
import com.capgemini.reports.reports.parameters.IChecker;
import com.capgemini.reports.reports.parameters.ProductUnit;
import com.capgemini.reports.reports.parameters.checker.ProductChecker;
import com.capgemini.reports.teamcenter.ObjectInfoHelper;
import com.capgemini.reports.teamcenter.queries.saved.ChangeRequestQuery;
import com.capgemini.reports.teamcenter.queries.saved.UnitFromChangeRequestQuery;
import com.google.common.base.Optional;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.Form;
import com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static com.capgemini.framework.teamcenter.TeamcenterObjectProperties.REVISION_LIST;
import static com.capgemini.framework.teamcenter.object.SnecmaObjectProperties.*;

/**
 * This class checks if a given "Liste de Modifications Retirées" is valid for a R20 report.
 */
@Getter
@Slf4j
public class R20LMRChecker implements IChecker {

    private static final Pattern YES_CHECKER = Pattern.compile(".*(Oui|Yes).*");

    private static final String MACE = SnecmaObjectTypes.PV4_MACE_REVISION;
    private static final String DCAD = SnecmaObjectTypes.PV4_DCAD_REVISION;
    private static final String CAD_ACR = SnecmaObjectTypes.PV4_CAD_ACR_REVISION;

    private final List<String> lmrList;
    private final String productId;
    private final ProductChecker productChecker; // Will provide the pruduct Item
    private final String unitBeforeEffectivity;

    /**
     * Simple constructor.
     *
     * @param lmrList           List<String> the list of modifications that need to be checked.
     * @param productUnitBefore ProductUnit
     * @param productChecker    this productChecker is supposed to have been executed prior to this check, so that its produc titem will be available for this test
     */
    public R20LMRChecker(final List<String> lmrList, final ProductUnit productUnitBefore, final ProductChecker productChecker) {
        this.lmrList = lmrList;
        this.productId = productUnitBefore.getProductID();
        this.unitBeforeEffectivity = productUnitBefore.getUnitStr();
        this.productChecker = productChecker;
    }

    private static boolean isUnitExceptionnel(final ItemRevision unitBefore) {
        Form form = unitBefore.get_item_master_tag();
        if (form != null) {
            String result = form.getPropertyObject(PV_UNIT_EXCEPTIONNEL).getStringValue();
            return YES_CHECKER.matcher(result).matches();
        } else {
            return false;
        }
    }

    @Override
    public void check(final TcSession session) throws InvalidUserInputException {
        // Retrieve the list of Units for the used Product
        List<ItemRevision> unitListOfProduct = retrieveListOfUnitsOfProduct(session);
        ItemRevision unitBefore = choseRevision(session, unitListOfProduct);
        List<ItemRevision> totalChangeRequestLMR = new ArrayList<>();
        for (String changeID : lmrList) {
            // retrieve the ChangeRequest
            ItemRevision changeRequestLmr = retrieveChangeRequest(session, changeID);
            totalChangeRequestLMR.add(changeRequestLmr);

            // Test unit exceptionnel
            boolean exceptionnel = isUnitExceptionnel(unitBefore);
            if (exceptionnel) {
                List<ItemRevision> listOfChangeOfUnitBefore = retrieveChangeRequestsOfUnit(session, unitBefore);

                int occurrencesInUnitBefore = occurrencesInListOfChangeOfUnit(changeID, listOfChangeOfUnitBefore);
                // No change found in the Unit Before
                if (occurrencesInUnitBefore == 0) {
                    throw new InvalidUserInputException(UserInputErrorEnum.REMOVED_CHANGE_REQUEST_IS_NOT_COMPATIBLE_WITH_BEFORE_EFFECTIVITY,
                            changeID);
                }
                // More than one revision found in the Unit Before
                else if (occurrencesInUnitBefore > 1) {
                    throw new InvalidUserInputException(
                            UserInputErrorEnum.SEVERAL_REVISIONS_OF_RETRIEVED_CHANGE_REQUEST_ARE_COMPATIBLE_WITH_BEFORE_EFFECTIVITY, changeID);
                }
            } else {
                // Retrieve the associated Units thanks to the pseudofolders
                List<ItemRevision> listUnitRevFromCR = new UnitFromChangeRequestQuery(session).searchID(changeID).findAll();
                if (listUnitRevFromCR.isEmpty()) {
                    throw new InvalidUserInputException(UserInputErrorEnum.REMOVED_CHANGE_REQUEST_IS_NOT_COMPATIBLE_WITH_BEFORE_EFFECTIVITY,
                            changeID);
                }

                // Find an appropriate Unit (contained in the list of units of the Product)
                ItemRevision selectedUnit = findAppropriateUnitInProductListOfUnits(session, unitListOfProduct, listUnitRevFromCR);
                if (selectedUnit == null) {
                    throw new InvalidUserInputException(UserInputErrorEnum.REMOVED_CHANGE_REQUEST_IS_NOT_COMPATIBLE_WITH_BEFORE_EFFECTIVITY,
                            changeID);
                }

                List<ItemRevision> listOfChangeOfSelectedUnit = retrieveChangeRequestsOfUnit(session, selectedUnit);
                int occurrencesInUnitBefore = occurrencesInListOfChangeOfUnit(changeID, listOfChangeOfSelectedUnit);
                if (occurrencesInUnitBefore > 1) {
                    throw new InvalidUserInputException(
                            UserInputErrorEnum.SEVERAL_REVISIONS_OF_RETRIEVED_CHANGE_REQUEST_ARE_COMPATIBLE_WITH_BEFORE_EFFECTIVITY, changeID);
                }
            }
        }
    }

    private ItemRevision findAppropriateUnitInProductListOfUnits(final TcSession session, final List<ItemRevision> unitListOfProduct, final List<ItemRevision> listUnitRevFromCR) {
        ItemRevision selectedUnit = null;
        String selectedRange = "";
        for (ItemRevision unit : listUnitRevFromCR) {
            String unitRange = UnitRevision.getRangeOfItemRevision(session, unit);
            if (unitRange == null) {
                continue;
            }
            if (unitListOfProduct.contains(unit) && Integer.parseInt(unitRange) <= Integer.parseInt(unitBeforeEffectivity)) {
                if (selectedUnit == null) {
                    selectedUnit = unit;
                    selectedRange = unitRange;
                }
                // Choose the revision
                if (Integer.parseInt(selectedRange) < Integer.parseInt(unitRange)) {
                    selectedUnit = unit;
                    selectedRange = unitRange;
                }
            }
        }
        return selectedUnit;
    }

    private int occurrencesInListOfChangeOfUnit(final String changeID, final List<ItemRevision> listOfChangeOfUnitBefore) {
        int indicatorIsinUnit = 0;
        for (ItemRevision changeOfUnitBefore : listOfChangeOfUnitBefore) {
            if (changeID.equals(changeOfUnitBefore.get_item_id())) {
                indicatorIsinUnit++;
            }
        }
        return indicatorIsinUnit;
    }

    private ItemRevision retrieveChangeRequest(final TcSession session, final String changeID) throws InvalidUserInputException {
        List<ItemRevision> listChangeRev = new ChangeRequestQuery(session).searchID(changeID).findAll();
        if (listChangeRev.isEmpty()) {
            throw new InvalidUserInputException(UserInputErrorEnum.UNKNOWN_CHANGE_REQUEST, changeID);
        }
        // ChangeRequest only have one revision
        ItemRevision theChange = listChangeRev.get(0);
        if (SnecmaObjectTypes.PV4_CR_DEPREC_REVISION.equals(theChange.getTypeObject().getName())) {
            throw new InvalidUserInputException(UserInputErrorEnum.REMOVED_CHANGE_REQUEST_IS_DEPRECATED, changeID);
        }
        return theChange;
    }

    private List<ItemRevision> retrieveChangeRequestsOfUnit(final TcSession session, final ItemRevision referenceUnit) {
        List<ItemRevision> changeRequestOfUnitBefore = new ArrayList<>();
        session.loadProperties(referenceUnit, PV_CHANGE_LIST);
        ModelObject[] changes = referenceUnit.getPropertyObject(PV_CHANGE_LIST).getModelObjectArrayValue();
        session.loadProperties(changes);
        for (ModelObject currentChange : changes) {
            if (currentChange == null) {
                log.warn("A Null ChangeRequest was obtained from the pseudo-folder 'PV_change_list' of a UnitRevision.");
                continue;
            }
            ItemRevision currentItemRev = (ItemRevision) currentChange;
            String objectTypeStr = currentItemRev.getTypeObject().getName();
            if (CAD_ACR.equals(objectTypeStr) || DCAD.equals(objectTypeStr) || MACE.equals(objectTypeStr)) {
                changeRequestOfUnitBefore.add(currentItemRev);
            }
        }
        return changeRequestOfUnitBefore;
    }

    private List<ItemRevision> retrieveListOfUnitsOfProduct(final TcSession session) {
        List<ItemRevision> unitListOfProduct = new ArrayList<>();
        Item product = productChecker.getProduct();
        session.loadProperties(product, REVISION_LIST);
        ModelObject[] productRevs = product.getPropertyObject(REVISION_LIST).getModelObjectArrayValue();
        session.loadProperties(productRevs);
        ItemRevision productRev = (ItemRevision) productRevs[0];

        session.loadProperties(productRev, PV_UNIT);
        ModelObject[] listOfUnitsBelowProduct = productRev.getPropertyObject(PV_UNIT).getModelObjectArrayValue();
        session.loadProperties(listOfUnitsBelowProduct);
        for (ModelObject unit : listOfUnitsBelowProduct) {
            if (unit != null) {
                unitListOfProduct.add((ItemRevision) unit);
            }
        }
        return unitListOfProduct;
    }

    private ItemRevision choseRevision(final TcSession session, final List<ItemRevision> unitListOfProduct) throws InvalidUserInputException {
        // Choose the revision of the Unit
        // Obtain the list of all the UnitRevision with same range
        List<ItemRevision> resultUnits = UnitRevision.getItemRevisionWithSameRange(session, unitBeforeEffectivity, unitListOfProduct);

        // Select the latest maturity unit
        Optional<RevisionStatusAndDate> potentialLatestUnitBefore = ObjectInfoHelper.getRevisionAndStatusWithMostRecentReleaseDate(session, resultUnits, null, StatusEnum.UNIT_MATURITY);

        if (!potentialLatestUnitBefore.isPresent()) {
            throw new InvalidUserInputException(UserInputErrorEnum.PRODUCT_AND_UNIT_DOES_NOT_EXIST);
        } else {
            RevisionStatusAndDate latestUnitBefore = potentialLatestUnitBefore.get();
            AbstractStatusFilter filter = StatusEnum.allow(ExecutionR20.R20_STATUS_BEFORE);

            // Verify the status of the latest maturity status
            if (filter.isIncluded(latestUnitBefore.getReleaseStatus().get_name())) {
                return latestUnitBefore.getItemRevision();
            } else {
                throw new InvalidUserInputException(UserInputErrorEnum.PRODUCT_AND_UNIT_DOES_NOT_EXIST);
            }
        }
    }
}
