package com.capgemini.reports.reports.parameters.checker;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.NotFoundException;
import com.capgemini.reports.exceptions.UserInputErrorEnum;
import com.capgemini.reports.reports.parameters.IChecker;
import com.capgemini.reports.reports.parameters.ProductUnit;
import com.capgemini.reports.teamcenter.ObjectInfoLoader;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.Effectivity;
import com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import com.teamcenter.soa.client.model.strong.ReleaseStatus;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

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

import static com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes.EFFECTIVITY;

/**
 * This class checks if a given UnitRevision is valid for a R20 report.<br>
 * It assumes that the Product and the unit exist (using a {@link ProductUnitChecker}. <br>
 * It fails if the unit does not have an effectivity matching the product ID.
 */
@Slf4j
public class UnitRevisionChecker implements IChecker {

    @Getter(value = AccessLevel.PROTECTED)
    private final ProductUnit productUnit;

    /**
     * Simple constructor.
     *
     * @param productUnit the {@link ProductUnit Product and the Unit} that need to be checked.
     */
    public UnitRevisionChecker(final ProductUnit productUnit) {
        this.productUnit = productUnit;
    }

    @Override
    public final void check(final TcSession session) throws InvalidUserInputException {
        checkUnits(session);
    }

    protected List<ItemRevision> checkUnits(final TcSession session) throws InvalidUserInputException {
        String productID = productUnit.getProductID();

        // Parse user input
        productUnit.parseUserUnit();
        int userUnit = productUnit.getUnit();

        // Get existing units in Database
        List<ItemRevision> units = checkUnitsOfProduct(session); // Fails if none found

        // Check if the user-input Unit exists in the list returned by Tc
        // No TcEffectivity could be found with matching end_item ID and a matching unit range: the check has FAILED, let's throw the correct Exception to explain Why
        return checkUnitsOfProductInRange(session, productID, userUnit, units);
    }

    private List<ItemRevision> checkUnitsOfProduct(final TcSession session) throws InvalidUserInputException {
        try {
            List<ItemRevision> theUnits = new ObjectInfoLoader(session).getUnitsOfProduct(productUnit);
            if (theUnits.isEmpty()) {
                throw unitNotAssociatedToProduct();
            }
            return theUnits;
        } catch (NotFoundException e) {
            throw new InvalidUserInputException(UserInputErrorEnum.PRODUCT_AND_UNIT_DOES_NOT_EXIST, e);
        }
    }

    private List<ItemRevision> checkUnitsOfProductInRange(final TcSession session, final String productID, final int userUnit, final List<ItemRevision> existingUnits)
            throws InvalidUserInputException {
        List<ItemRevision> unitsInRange = new ArrayList<>();
        for (ItemRevision existingTcUnit : existingUnits) {
            session.loadProperties(existingTcUnit, "release_status_list");
            ReleaseStatus[] releaseStatuses = existingTcUnit.get_release_status_list();
            session.loadProperties(releaseStatuses);
            if (unitHasEffectivityInRange(session, releaseStatuses, productID, userUnit)) {
                unitsInRange.add(existingTcUnit);
            }
        }
        if (unitsInRange.isEmpty()) {
            throw unitNotAssociatedToProduct();
        } else {
            return unitsInRange;
        }
    }

    private boolean unitHasEffectivityInRange(final TcSession session, final ReleaseStatus[] releaseStatusList, final String productID, int userUnit) {
        for (ReleaseStatus releaseStatus : releaseStatusList) {
            if (!EFFECTIVITY.equals(releaseStatus.get_name())) {
                continue; // skip
            }
            session.loadProperties(releaseStatus, "effectivities");
            ModelObject[] effectivities = releaseStatus.get_effectivities();
            session.loadProperties(effectivities);
            for (ModelObject effectivityMO : effectivities) {
                Effectivity effectivity = (Effectivity) effectivityMO;
                session.loadProperties(effectivity, "end_item");
                Item endItem = effectivity.get_end_item();
                session.loadProperties(endItem);
                if (endItem.get_item_id().equals(productID) && sameUnit(effectivity.get_unit_range_text(), userUnit)) {
                    // This Unit Revision has an effectivity with a matching end item and unit range! the test is PASSED
                    return true;
                }
            }
        }
        return false;
    }

    protected InvalidUserInputException unitNotAssociatedToProduct() {
        UserInputErrorEnum productAndUnitDoesNotExist = UserInputErrorEnum.PRODUCT_AND_UNIT_DOES_NOT_EXIST;
        log.error(productAndUnitDoesNotExist.getMsgEn());
        return new InvalidUserInputException(productAndUnitDoesNotExist);
    }

    protected InvalidUserInputException errorMessage() {
        UserInputErrorEnum productAndUnitDoesNotExist = UserInputErrorEnum.PRODUCT_AND_UNIT_DOES_NOT_EXIST;
        log.error(productAndUnitDoesNotExist.getMsgEn());
        return new InvalidUserInputException(productAndUnitDoesNotExist);
    }

    private boolean sameUnit(final String tcUnitStr, final int userUnit) {
        return Integer.valueOf(tcUnitStr) == userUnit;
    }
}
