package com.capgemini.reports.definition.r26report;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.EmptyNomenclatureException;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
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.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.BOMLineHolderFactory;
import com.capgemini.reports.bomlineholder.processor.DefaultAltOrDetBehaviour;
import com.capgemini.reports.filter.AllIsGoodFilter;
import com.capgemini.reports.reports.parameters.EffectivityDate;
import com.capgemini.reports.snecmaobjects.product.ProductRevision;
import com.capgemini.reports.teamcenter.resolution.structureservice.SingleStructureServiceCaller;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.BOMLine;
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 com.teamcenter.soa.client.model.strong.RevisionRule;
import com.teamcenter.soa.client.model.strong.WorkspaceObject;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * Manager for making lines in report R26.
 */
@Getter
class LineManager {

  private static final AllIsGoodFilter ACCEPT_ALL = new AllIsGoodFilter();

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

  private TcSession session;
  private ExecutionR26 report;
  private String componentId;
  private RevisionRule revisionRuleR26;
  private EffectivityDate effectivityDate;
  private List<R26BomLine> r26BomLines;
  private TcRevisionRulesConfiguration revRulesConfiguration;
  private RevisionRuleManager revisionRuleManager;

  LineManager(TcSession session, ExecutionR26 report, RevisionRuleManager revisionRuleManager) throws KurtException {
    this.session = session;
    this.report = report;
    this.componentId = report.getComponent();
    this.r26BomLines = new ArrayList<>();
    this.revisionRuleR26 = revisionRuleManager.getRevisionRule();
    this.effectivityDate = report.getEffectivityDate();
    this.revisionRuleManager = revisionRuleManager;

    TcRevisionRuleProperties revisionRuleProperties = new TcRevisionRuleProperties(effectivityDate.getDateTime());
    TcRevisionRule revisionRule = session.findRevRule(report.getRevisionRule());
    revRulesConfiguration = new TcRevisionRulesConfiguration(revisionRule, revisionRuleProperties);
  }

  /**
   * Ajoute les lignes de nomenclature au rapport R26.
   */
  void addLines(ItemRevision receptor, ItemRevision rf, ProductRevision product, ItemRevision module, String effectivity,
                Boolean consumedByProduct, Boolean effectivityUnit) throws KurtException {
    BOMLineHolder unBuiltTopLine = getBomLineHolder(rf == null ? receptor : rf);
    if (unBuiltTopLine != null) {
      for (BOMLineHolder currentChild : unBuiltTopLine.getBomChilds()) {
        ItemRevision itemRev = currentChild.getItemRevision();
        if (currentChild.getBomChilds().isEmpty() && componentId.equals(itemRev.get_item_id())) {
          addLineToR26(currentChild.getBomLineObject(), receptor, rf, product, module,
            effectivity, consumedByProduct, effectivityUnit);
        }
      }
    }
  }

  /**
   * Retourne la bom line générée avec l'objet topWo ou null si elle n'existe pas.
   */
  private BOMLineHolder getBomLineHolder(WorkspaceObject topWo) throws KurtException {
    BOMLineHolder unBuiltTopLine;
    TcBomObject topLineObject = new TcBomObjectFactory(session, topWo).create();

    try (TcBomWindow bomWindow = session.createBomWindow(new TcBomWindowConfiguration(topLineObject, revRulesConfiguration))) {
      // Perform the job
      try {
        unBuiltTopLine = new SingleStructureServiceCaller(session).resolve(bomWindow.topLine(), new R26BOMLineExplorer());

        List<Form> itemRevMaster = unBuiltTopLine.getAllItemRevMasters();
        session.loadProperties(itemRevMaster.toArray(new ModelObject[0]));

        BOMLineHolderFactory.buildTopBom(session, unBuiltTopLine, ACCEPT_ALL);
        DefaultAltOrDetBehaviour<Object, Object> aBOMLineHolderProcessor = new DefaultAltOrDetBehaviour<>(session,
          effectivityDate.isNow() ? null : effectivityDate.getDateTime());
        unBuiltTopLine.process(aBOMLineHolderProcessor);
        unBuiltTopLine.freeze();
      }
      catch (EmptyNomenclatureException e) {
        unBuiltTopLine = null;
      }
    }
    return unBuiltTopLine;
  }

  /**
   * Ajoute la ligne de nomenclature aux lignes du rapport R26 si elle n'existe pas déjà.
   */
  private void addLineToR26(BOMLine line, ItemRevision receptor, ItemRevision rf, ProductRevision product, ItemRevision module,
                            String effectivity, Boolean consumedByProduct, Boolean effectivityUnit) {
    if (line != null) {
      R26BomLine r26BomLine = new R26BomLine(line.get_bl_quantity(), line.get_bl_item_uom_tag(),
        receptor, rf, product, module, effectivity, consumedByProduct, effectivityUnit);
      consolidateR26BomLines(r26BomLine);
      if (!isLineAlreadyAdded(r26BomLine)) {
        r26BomLines.add(r26BomLine);
      }
    }
  }

  /**
   * Retourne true si la ligne existe déjà dans le rapport (toutes les valeurs sont identiques).
   * Sinon si seule l'effectivté est différente et en Produit/Unit alors la ligne existante sera supprimée
   * et une nouvelle ligne avec les effectivités cumulées sera ajoutée.
   */
  private boolean isLineAlreadyAdded(R26BomLine r26BomLine) {
    for (R26BomLine currentLine : r26BomLines) {
      if (r26BomLine.equals(currentLine)) {
        return true;
      }
    }
    return false;
  }

  private void consolidateR26BomLines(R26BomLine r26BomLine) {
    List<R26BomLine> r26BomLinesToDelete = new ArrayList<>();
    for (R26BomLine currentLine : r26BomLines) {
      if (r26BomLine.equals(currentLine) && r26BomLine.getConsumedByProduct() && !currentLine.getConsumedByProduct()) {
        r26BomLinesToDelete.add(currentLine);
      }
      if (r26BomLine.equals(currentLine) && r26BomLine.equalsExceptEffectivity(currentLine) && r26BomLine.isEffectivityUnit()) {
        // cumulate effectivities
        String eff1 = currentLine.getEffectivity();
        String eff2 = r26BomLine.getEffectivity();
        r26BomLine.setEffectivity(eff1 + ";" + eff2);
        r26BomLinesToDelete.add(currentLine);
      }
    }
    for (R26BomLine r26BomLineToDelete : r26BomLinesToDelete) {
      r26BomLines.remove(r26BomLineToDelete);
    }
  }

  /**
   * Traitement des Generic/Generic Assemb.
   * Pour chacun: ajout de la ligne de nomenclatures trouvée au rapport R26.
   * Si aucun Generic/Generic Assemb: la ligne est ajoutée sans Module et sera visible en rouge car non consommée par un produit.
   */
  void manageGenerics(Map<ItemRevision, Boolean> generics, ItemRevision firstLevelParentRev, ItemRevision secondLevelParentRev,
                      ProductRevision productRev, String effectivity, Boolean effectivityUnit) throws KurtException {

    for (Map.Entry<ItemRevision, Boolean> currentGenerics : generics.entrySet()) {
      LOGGER.info("=== Treating Generic: {} ===",currentGenerics.getKey().get_item_revision_id());
      if (SnecmaObjectTypes.isPartRevision(firstLevelParentRev.getTypeObject().getName())) {
        addLines(firstLevelParentRev, null, productRev, currentGenerics.getKey(), "", currentGenerics.getValue(), effectivityUnit);
      }
      else {
        addLines(secondLevelParentRev, firstLevelParentRev, productRev, currentGenerics.getKey(), effectivity,
          currentGenerics.getValue(), effectivityUnit);
      }
    }

    // produit à noté en rouge, le composant de la ligne n'est pas consommé
    if (generics.isEmpty() && !SnecmaObjectTypes.isPartRevision(firstLevelParentRev.getTypeObject().getName())) {
      addLines(secondLevelParentRev, firstLevelParentRev, productRev, null, effectivity, false, effectivityUnit);
    }
  }

  /**
   * Récupère tous les Generic/Generic Assemb lors de la remontée via where used
   */
  void addGenerics(Map<ItemRevision, Boolean> generics, ItemRevision itemRev, TcEffectivity tcEffectivity, String productItemId, Item item) {
    List<ItemRevision> parents = session.whereUsed(itemRev, 1, revisionRuleR26);
    for (ItemRevision currentParent : parents) {
      if (SnecmaObjectTypes.isGenericRevision(currentParent.getTypeObject().getName())) {
        if (EffectivityHelper.isEffectivityMatches(session, tcEffectivity, currentParent)) {
          // tester si le composant est non standard
          if (!item.get_object_type().equals(SnecmaObjectTypes.STANDARD)) {
            boolean productFound = findProduct(revisionRuleR26, productItemId, currentParent);
            generics.put(currentParent, productFound);
          } else {
            generics.put(currentParent, false);
          }
        }
      } else {
        addGenerics(generics, currentParent, tcEffectivity, productItemId, item);
      }
    }
  }

  /**
   * Manage le RF en Produit/Unit (currentRfPU):
   * Pour chaque effectivité trouvée sur le statut Effectivity:
   * La règle de révision du rapport est mise à jour avec le produit et les units du statut
   * Tous les Generic/Generic Assemb trouvés sont ensuite gérés
   */
  void manageRfPU(ItemRevision firstLevelParentRev, ItemRevision secondLevelParentRev, ItemRevision currentRfPU, Item item) throws KurtException {
    Map<TcEffectivity, ItemRevision> tcEffectivities = EffectivityHelper.getEffectivitiesWithUnitRange(session, currentRfPU);
    // récuperer les effectivitées
    for (Map.Entry<TcEffectivity, ItemRevision> currentEff : tcEffectivities.entrySet()) {
      int[] units = currentEff.getKey().unitNumbers();
      Item product = currentEff.getKey().endItem();

      revisionRuleManager.setProductUnitRevisionRuleProperties(product.getUid(), units[0]);

      // récupérer tous les Generic/Generic Assemb trouvés via le where used.
      Map<ItemRevision, Boolean> generics = new HashMap<>();
      addGenerics(generics, currentRfPU, currentEff.getKey(), product.get_item_id(), item);

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

      manageGenerics(generics, firstLevelParentRev, secondLevelParentRev, productRev,
        effectivityDate.formatAsInputCriteria(), false);
    }
  }

  /**
   * Retourne true si la ligne de nomenclature entre parentRev et childRev existe, false sinon.
   */
  boolean bomLineExists(ItemRevision parentRev, String childRevId) throws KurtException {
    session.loadProperties(parentRev, "items_tag");
    BOMLineHolder unBuiltTopLine = getBomLineHolder(parentRev.get_items_tag());
    if (unBuiltTopLine != null && unBuiltTopLine.getItemRevision().get_item_revision_id().equals(parentRev.get_item_revision_id())) {
      for (BOMLineHolder currentChild : unBuiltTopLine.getBomChilds()) {
        ItemRevision itemRev = currentChild.getItemRevision();
        if (childRevId.equals(itemRev.get_item_id())) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Retourne true si l'itemRev est consommé par un produit, false sinon.
   */
  boolean findProduct(RevisionRule revRule, String productItemId, ItemRevision itemRev) {
    List<ItemRevision> parents = session.whereUsed(itemRev, 1, revRule);
    boolean productFound = false;
    // remonter au produit: si trouvé alors true, false sinon
    for (ItemRevision currentParent : parents) {
      if (SnecmaObjectTypes.isProductRevision(currentParent.getTypeObject().getName()) && productItemId.equals(currentParent.get_item_id())) {
        return true;
      }
      else {
        productFound = productFound || findProduct(revRule, productItemId, currentParent);
      }
    }
    return productFound;
  }
}
