package com.capgemini.reports.definition.r26report;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.object.SnecmaObjectProperties;
import com.capgemini.framework.teamcenter.object.pom.status.TcEffectivity;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcBomObject;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcBomObjectFactory;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcRevisionRule;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcRevisionRuleProperties;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindow;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindowConfiguration;
import com.capgemini.framework.teamcenter.object.runtime.TcRevisionRulesConfiguration;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.framework.teamcenter.queries.saved.modelobject.ItemQuery;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.definition.r26report.ExecutionR26.ReportDataR26;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.UserInputErrorEnum;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.execution.strategies.IExecutionStrategy;
import com.capgemini.reports.filter.AllIsGoodFilter;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.reports.parameters.EffectivityDate;
import com.capgemini.reports.snecmaobjects.product.ProductRevision;
import com.capgemini.reports.teamcenter.queries.TypeQuery;
import com.capgemini.reports.teamcenter.resolution.structureservice.SingleStructureServiceCaller;
import com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import com.teamcenter.soa.client.model.strong.RevisionRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class will allow to prepare data and findAll back data for making report R26.
 */
public class DataReportPreparatorR26 implements IDataReportPreparator<ExecutionR26> {

  private static final long serialVersionUID = -8429205549743626211L;

  private static final Logger LOGGER = LoggerFactory.getLogger(DataReportPreparatorR26.class);

  @Override
  public String print(TcSession session, ExecutionR26 execution, IExecutionStrategy executionStrategy) throws KurtException {
    PrintParameters printParameters = prepareData(session, execution);
    executionStrategy.print(printParameters, execution);
    return executionStrategy.getReportFileName(execution) + printParameters.getOuputFileExtension();
  }

  private PrintParameters prepareData(final TcSession session, final ExecutionR26 report) throws KurtException {
    LOGGER.debug("PrepareData started on: {}", report.getReportIdentifier().getReportTitle());

    String componentId = report.getComponent();

    List<Item> items = new ItemQuery(session).searchID(componentId).findAll();
    if(items.isEmpty()){
      throw new InvalidUserInputException(UserInputErrorEnum.OBJECT_DOES_NOT_EXIST);
    }

    Item item = null;

    // Depuis qu'il est possible d'avoir le meme item id sur une part et un document il faut parcourir la liste pour
    // ne récuperer que l'objet part.
    for(Item currentItem : items) {
      if(SnecmaObjectTypes.isPart(currentItem.getTypeObject().getName())) {
        item = currentItem;
        break;
      }
    }

    if(item == null) {
      throw new InvalidUserInputException(UserInputErrorEnum.THE_SELECTED_OBJECT_IS_NOT_PERMITTED_ON_THIS_REPORT);
    }

    session.loadProperties(item, SnecmaObjectProperties.OBJECT_TYPE);

    List<RevisionRule> list = TypeQuery.getRevisionRule(session, report.getRevisionRule());
    RevisionRule reportRevisionRule = list.get(0);

    String revisionRuleNameForThisR26 = "R26_" + componentId + "_" + report.getReportTechnicalUid();
    try {
      RevisionRuleManager revisionRuleManager = new RevisionRuleManager(session, reportRevisionRule, revisionRuleNameForThisR26);
      LineManager lineManager = new LineManager(session, report, revisionRuleManager);

      session.setObjectPropPolicy(report.getFullPolicy());

      EffectivityDate effectivityDate = report.getEffectivityDate();
      TcRevisionRuleProperties revisionRuleProperties = new TcRevisionRuleProperties(effectivityDate.isNow() ? null : effectivityDate.getDateTime());
      TcRevisionRule revisionRule = session.findRevRule(report.getRevisionRule());
      TcRevisionRulesConfiguration revRulesConfiguration = new TcRevisionRulesConfiguration(revisionRule, revisionRuleProperties);
      List<ItemRevision> firstLevelParents = session.whereUsed(resolveGoodRevision(session, report, item, revRulesConfiguration), 1, reportRevisionRule);

      for (ItemRevision firstLevelParentRev : firstLevelParents) {
        session.loadProperties(firstLevelParentRev, "object_name","item_revision_id","item_revision");
        LOGGER.info("=== Treating object at level 1: {}/{} ===",firstLevelParentRev.get_item_id(),firstLevelParentRev.get_item_revision_id());
        List<ItemRevision> secondLevelParents = session.whereUsed(firstLevelParentRev, 1, reportRevisionRule);
        if (secondLevelParents.isEmpty()) {
          LOGGER.debug("Second Level empty");
          if (SnecmaObjectTypes.isPartRevision(firstLevelParentRev.getTypeObject().getName())) {
            // si il n'y a pas de second level alors pas d'effectivité, ni produit, ni module
            // cf: SFD:5.8.3 Remontée de nomenclature
            if (lineManager.bomLineExists(firstLevelParentRev, componentId)) {
              // cas où plusieurs révisions disponibles: une seule est valide avec la règle de révision du R26
              lineManager.addLines(firstLevelParentRev, null, null, null, "",
                false, false);
            }
          }
          else {
            // Le composant n'est pas lié à un RF à Date valide (donc lié à un RF à Date obsolète) -> la ligne de nomenclature n'est pas traitée
            // Ou il est lié à un RF en PU -> la ligne de nomenclature est traitée

            Map<TcEffectivity, ItemRevision> tcEffectivities = EffectivityHelper.getEffectivitiesWithUnitRange(session,
              firstLevelParentRev);

            // récupérer les effectivitées
            for (Map.Entry<TcEffectivity, ItemRevision> currentTcEff : tcEffectivities.entrySet()) {

              int[] units = currentTcEff.getKey().unitNumbers();
              Item product = currentTcEff.getKey().endItem();
              String productItemId = product.get_item_id();

              session.loadProperties(product, "revision_list", "item_revision");
              ProductRevision productRev = new ProductRevision(session, (ItemRevision) (product.get_revision_list()[0]));

              // remonter au Generic avec DMA Configuration Interface Product Unit
              revisionRuleManager.setProductUnitRevisionRuleProperties(product.getUid(), units[0]);
              secondLevelParents = session.whereUsed(firstLevelParentRev, 1, revisionRuleManager.getRevisionRule());

              LOGGER.info("=== Launching DMA Configuration Interface Product Unit on part {}/{} with Product {}/{} Unit {} ",firstLevelParentRev.get_item_id(),firstLevelParentRev.get_item_revision(),product.get_item_id(),product.get_item_revision(),units[0]);

              LOGGER.info("We found {} parents", secondLevelParents.size());

              Map<String, List<Range>> itemRangeMap = new HashMap<>();

              for (ItemRevision currentParent : secondLevelParents) {

                LOGGER.info("checking parent {}/{}",currentParent.get_item_id(),currentParent.get_item_revision_id());

                if (SnecmaObjectTypes.isGenericRevision(currentParent.getTypeObject().getName())) {
                  LOGGER.debug("Currently on generic {}/{} ",currentParent.get_item_id(),currentParent.get_item_revision_id());
                  // récupère les effectivités valide
                  String effectivityRangeUnit = EffectivityHelper.calculateEffectivity(session, firstLevelParentRev, product, itemRangeMap);

                  if (EffectivityHelper.isEffectivityMatches(session, currentTcEff.getKey(), currentParent)) {
                    // tester si le composant est non standard
                    if (!item.get_object_type().equals(SnecmaObjectTypes.STANDARD)) {
                      LOGGER.debug("Effectivity matches {} {} ", currentTcEff.getKey().endItem(), currentTcEff.getKey().unitNumbers());
                      boolean productFound = lineManager.findProduct(revisionRuleManager.getRevisionRule(), productItemId, currentParent);
                      lineManager.addLines(currentParent, firstLevelParentRev, productRev, currentParent,
                              effectivityRangeUnit, productFound, true);
                    } else {
                      // The Component is a standard. We will not check that the product consume the component.
                      lineManager.addLines(currentParent, firstLevelParentRev, productRev, currentParent,
                              effectivityRangeUnit, false, true);
                    }
                  } else {
                    LOGGER.debug("Effectivity doesn't matches {} ", currentTcEff.getKey().unitNumbers());
                  }
                } else if(!SnecmaObjectTypes.isSuperModelRevision(currentParent.getTypeObject().getName())){
                  // remonter au dernier Generic trouvé
                  Map<ItemRevision, Boolean> generics = new HashMap<>();
                  lineManager.addGenerics(generics, currentParent, currentTcEff.getKey(), productItemId, item);

                  // récupère les effectivités valide
                  String effectivityRangeUnit = EffectivityHelper.calculateEffectivity(session, firstLevelParentRev, product, itemRangeMap);

                  lineManager.manageGenerics(generics, firstLevelParentRev, currentParent, productRev,
                    effectivityRangeUnit, true);
                }
              }
            }
          }
        }
        else {
          LOGGER.debug("Navigate second level");
          for (ItemRevision secondLevelParentRev : secondLevelParents) {

            List<ItemRevision> rfPUs = RfPUManager.getRfPURevisions(session, reportRevisionRule, secondLevelParentRev);
            for (ItemRevision currentRfPU : rfPUs) {
              lineManager.manageRfPU(firstLevelParentRev, secondLevelParentRev, currentRfPU, item);
            }

            if (rfPUs.isEmpty() && SnecmaObjectTypes.isPartRevision(secondLevelParentRev.getTypeObject().getName())
              && lineManager.bomLineExists(secondLevelParentRev, firstLevelParentRev.get_item_id())) {

              if (SnecmaObjectTypes.isPartRevision(firstLevelParentRev.getTypeObject().getName())) {
                // Pas de RF, le Récepteur est une Part
                lineManager.addLines(firstLevelParentRev, null, null, null,
                  report.getEffectivityDate().formatAsInputCriteria(), false, false);
              }
              else {
                // Le récepteur est une part et le RF est à date
                lineManager.addLines(secondLevelParentRev, firstLevelParentRev, null, null,
                  report.getEffectivityDate().formatAsInputCriteria(), false, false);
              }
            }
          }
        }
      }

      ReportDataR26 reportDataR26 = new ReportDataR26(lineManager.getR26BomLines());
      reportDataR26.setStandard(item.get_object_type().equals(SnecmaObjectTypes.STANDARD));
      LOGGER.debug("The component is a standard = {}", reportDataR26.isStandard());
      R26Printer printer = new R26Printer();
      PrintParameters printParameters = printer.makeMasterTemplate(report, reportDataR26);
      printer.constructDataMaps(session, printParameters, reportDataR26);
      LOGGER.debug("PrepareData ended on: {}", report.getReportIdentifier().getReportTitle());
      return printParameters;
    }
    finally {
      // Suppression de la règle de révision créée pour ce Rapport R26
      List<RevisionRule> revisionRuleList = TypeQuery.getRevisionRule(session, revisionRuleNameForThisR26);
      if (!revisionRuleList.isEmpty()) {
        session.deleteObjects(revisionRuleList.get(0));
      }
    }
  }

  private ItemRevision resolveGoodRevision(TcSession session, ExecutionR26 report, Item item, TcRevisionRulesConfiguration revRulesConfiguration) throws KurtException {
    TcBomObject topLineObject = new TcBomObjectFactory(session, item).create();
    TcBomWindowConfiguration tcBomWindowConfiguration = new TcBomWindowConfiguration(topLineObject, revRulesConfiguration);
    try (TcBomWindow bomWindow = session.createBomWindow(tcBomWindowConfiguration)) {
      session.setObjectPropPolicy(report.getFullPolicy());
      BOMLineHolder unbuiltTopLine = new SingleStructureServiceCaller(session).resolve(bomWindow.topLine(), new AllIsGoodFilter());
      return unbuiltTopLine.getItemRevision();
    }
  }
}
