package com.axelor.apps.supplychain.service;

import com.axelor.apps.account.db.PaymentCondition;
import com.axelor.apps.base.db.Partner;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.purchase.db.PurchaseOrder;
import com.axelor.apps.purchase.db.PurchaseOrderLine;
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
import com.axelor.apps.stock.db.StockMove;
import com.axelor.apps.stock.db.StockMoveLine;
import com.axelor.apps.stock.db.repo.StockMoveRepository;
import com.axelor.apps.supplychain.db.SupplierRating;
import com.axelor.apps.supplychain.db.repo.SupplierRatingRepository;
import com.axelor.inject.Beans;
import com.google.inject.persist.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class SupplierRatingServiceImpl implements SupplierRatingService {

  public final int COEF_PAYMENT_DELAY = 1;
  public final int COEF_DELIVERY_TIME = 1;
  public final int COEF_COMPLIANCE_WITH_TECHNICAL_SPECIFICATIONS = 3;
  public final int COEF_PRICE = 3;
  public final int COEF_QUANTITY_COMPLIANCE = 1;
  public final int COEF_COMPLAINT_MANAGEMENT = 1;
  public final int COEF_PRICE_STABILITY = 1;
  public final int SUM_COEF =
      COEF_PAYMENT_DELAY
          + COEF_DELIVERY_TIME
          + COEF_COMPLIANCE_WITH_TECHNICAL_SPECIFICATIONS
          + COEF_PRICE
          + COEF_QUANTITY_COMPLIANCE
          + COEF_COMPLAINT_MANAGEMENT
          + COEF_PRICE_STABILITY;

  public void calculatRating(SupplierRating supplierRating) {
    Integer paymentDelayRatingSelect = supplierRating.getPaymentDelayRatingSelect();
    Integer deliveryTimeRatingSelect = supplierRating.getDeliveryTimeRatingSelect();
    Integer complianceWithTechnicalSpecificationsRatingSelect =
        supplierRating.getComplianceWithTechnicalSpecificationsRatingSelect();
    Integer priceRatingSelect = supplierRating.getPriceRatingSelect();
    Integer quantityComplianceRatingSelect = supplierRating.getQuantityComplianceRatingSelect();
    Integer complaintManagementRatingSelect = supplierRating.getComplaintManagementRatingSelect();
    Integer priceStabilityRatingSelect = supplierRating.getPriceStabilityRatingSelect();

    // Calculate the overall score based on ratings
    int score =
        (paymentDelayRatingSelect * COEF_PAYMENT_DELAY)
            + (deliveryTimeRatingSelect * COEF_DELIVERY_TIME)
            + (complianceWithTechnicalSpecificationsRatingSelect
                * COEF_COMPLIANCE_WITH_TECHNICAL_SPECIFICATIONS)
            + (priceRatingSelect * COEF_PRICE)
            + (quantityComplianceRatingSelect * COEF_QUANTITY_COMPLIANCE)
            + (complaintManagementRatingSelect * COEF_COMPLAINT_MANAGEMENT)
            + (priceStabilityRatingSelect * COEF_PRICE_STABILITY);

    // Set the overall score
    BigDecimal overallScore =
        BigDecimal.valueOf(score * 20)
            .divide(BigDecimal.valueOf(SUM_COEF * 3), 2, BigDecimal.ROUND_HALF_UP);
    supplierRating.setOverallScore(overallScore);
  }

  @Transactional
  public void createRating(StockMove stockMove) {

    StockMove stockMoveOrigin = stockMove.getStockMoveOrigin();
    Boolean isWithBackorder = stockMove.getIsWithBackorder();

    if (stockMoveOrigin == null) {
      LocalDateTime realDate;
      if (isWithBackorder == true) {
        realDate = stockMove.getRealDate();
      } else {
        realDate = LocalDateTime.now();
      }

      Partner partner = stockMove.getPartner();

      // Stock Move Lines
      List<StockMoveLine> stockMoveLinesList = stockMove.getStockMoveLineList();

      // Purchase Order
      Long originId = stockMove.getOriginId();
      PurchaseOrder purchaseOrder = Beans.get(PurchaseOrderRepository.class).find(originId);

      // Purchase Order Lines
      List<PurchaseOrderLine> purchaseOrderLinesList = purchaseOrder.getPurchaseOrderLineList();

      // Délai de payment
      PaymentCondition paymentCondition = purchaseOrder.getPaymentCondition();
      Integer paymentTime = paymentCondition.getPaymentTime();
      Integer paymentDelayRatingSelect = calculatPaymentDelayRatingSelect(paymentTime);

      // Date
      LocalDate estimatedDate = purchaseOrder.getDeliveryDate();
      Integer deliveryTimeRatingSelect = calculatDeliveryTimeRatingSelect(estimatedDate, realDate);

      // Quantité
      Integer quantityComplianceRatingSelect =
          calculatQuantityComplianceRatingSelect(stockMoveLinesList);

      // Stabilité des prix
      Integer priceStabilityRatingSelect =
          calculatPriceStabilityRatingSelectLastPrice(purchaseOrderLinesList, partner);

      // Creation
      SupplierRating supplierRating = new SupplierRating();
      supplierRating.setPaymentDelayRatingSelect(paymentDelayRatingSelect);
      supplierRating.setDeliveryTimeRatingSelect(deliveryTimeRatingSelect);
      supplierRating.setQuantityComplianceRatingSelect(quantityComplianceRatingSelect);
      supplierRating.setPriceStabilityRatingSelect(priceStabilityRatingSelect);
      supplierRating.setPurchaseOrder(purchaseOrder);
      supplierRating.setSupplierPartner(partner);
      Beans.get(SupplierRatingRepository.class).save(supplierRating);

    } else {
      // Purchase Order
      Long originId = stockMove.getOriginId();
      PurchaseOrder purchaseOrder = Beans.get(PurchaseOrderRepository.class).find(originId);

      List<StockMove> originStockMoves =
          Beans.get(StockMoveRepository.class)
              .all()
              .fetch()
              .stream()
              .filter(t -> t.getOriginId() == originId && t.getStatusSelect() == 3)
              .collect(Collectors.toList());

      // Purchase Order Lines
      List<PurchaseOrderLine> purchaseOrderLinesList = purchaseOrder.getPurchaseOrderLineList();

      // Related Stock Moves Lines
      List<StockMoveLine> originStockMovesLines = new ArrayList<>();
      for (StockMove originStockMove : originStockMoves) {
        // Stock Move Lines
        List<StockMoveLine> originStockMovesLinesList = originStockMove.getStockMoveLineList();
        originStockMovesLines.addAll(originStockMovesLinesList);
      }
      // Quantité
      Integer quantityComplianceRatingSelect =
          calculatQuantityComplianceRatingSelect(purchaseOrderLinesList, originStockMovesLines);

      // update
      SupplierRating supplierRating = purchaseOrder.getSupplierRating();
      supplierRating.setQuantityComplianceRatingSelect(quantityComplianceRatingSelect);
    }
  }

  private Integer calculatPaymentDelayRatingSelect(Integer paymentTime) {
    Integer paymentDelayRatingSelect;
    if (paymentTime == 0) {
      paymentDelayRatingSelect = 0;
    } else if (paymentTime <= 15) {
      paymentDelayRatingSelect = 1;
    } else if (paymentTime <= 30) {
      paymentDelayRatingSelect = 2;
    } else {
      paymentDelayRatingSelect = 3;
    }
    return paymentDelayRatingSelect;
  }

  private Integer calculatDeliveryTimeRatingSelect(
      LocalDate estimatedDate, LocalDateTime realDate) {
    int deliveryTimeRatingSelect;
    if (realDate.toLocalDate().isBefore(estimatedDate.plusDays(1))) {
      deliveryTimeRatingSelect = 3;
    } else if (realDate.toLocalDate().isBefore(estimatedDate.plusDays(5))) {
      deliveryTimeRatingSelect = 2;
    } else if (realDate.toLocalDate().isBefore(estimatedDate.plusDays(15))) {
      deliveryTimeRatingSelect = 1;
    } else {
      deliveryTimeRatingSelect = 0;
    }
    return deliveryTimeRatingSelect;
  }

  private Integer calculatQuantityComplianceRatingSelect(List<StockMoveLine> stockMoveLinesList) {
    // Calculate total ordered quantity
    BigDecimal totalOrderedQty = BigDecimal.ZERO;
    BigDecimal totalReceivedQty = BigDecimal.ZERO;
    for (StockMoveLine stockMoveLine : stockMoveLinesList) {
      BigDecimal orderedQty = stockMoveLine.getQty();
      totalOrderedQty = totalOrderedQty.add(orderedQty);
      BigDecimal receivedQty = stockMoveLine.getRealQty();
      totalReceivedQty = totalReceivedQty.add(receivedQty);
    }

    // Calculate quantity compliance
    BigDecimal compliancePercentage;
    if (totalOrderedQty.compareTo(BigDecimal.ZERO) != 0) {
      compliancePercentage =
          totalReceivedQty
              .divide(totalOrderedQty, 2, RoundingMode.HALF_UP)
              .multiply(BigDecimal.valueOf(100));
    } else {
      // Handle division by zero if necessary
      compliancePercentage = BigDecimal.ZERO;
    }

    System.out.println("totalOrderedQty1:" + totalOrderedQty);
    System.out.println("totalReceivedQty1:" + totalReceivedQty);
    System.out.println("compliancePercentage1:" + compliancePercentage);

    // Determine quantityComplianceRatingSelect based on compliancePercentage
    Integer quantityComplianceRatingSelect;
    if (compliancePercentage.compareTo(BigDecimal.valueOf(100)) >= 0) {
      quantityComplianceRatingSelect = 3;
    } else if (compliancePercentage.compareTo(BigDecimal.valueOf(80)) >= 0) {
      quantityComplianceRatingSelect = 2;
    } else if (compliancePercentage.compareTo(BigDecimal.valueOf(70)) >= 0) {
      quantityComplianceRatingSelect = 1;
    } else {
      quantityComplianceRatingSelect = 0;
    }
    return quantityComplianceRatingSelect;
  }

  private Integer calculatQuantityComplianceRatingSelect(
      List<PurchaseOrderLine> purchaseOrderLineslist, List<StockMoveLine> stockMoveLinesList) {
    // Calculate total ordered quantity
    BigDecimal totalOrderedQty = BigDecimal.ZERO;
    BigDecimal totalReceivedQty = BigDecimal.ZERO;
    for (StockMoveLine stockMoveLine : stockMoveLinesList) {
      BigDecimal receivedQty = stockMoveLine.getRealQty();
      totalReceivedQty = totalReceivedQty.add(receivedQty);
    }

    for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLineslist) {
      BigDecimal orderedQty = purchaseOrderLine.getQty();
      totalOrderedQty = totalOrderedQty.add(orderedQty);
    }
    // Calculate quantity compliance
    BigDecimal compliancePercentage;
    if (totalOrderedQty.compareTo(BigDecimal.ZERO) != 0) {
      compliancePercentage =
          totalReceivedQty
              .divide(totalOrderedQty, 2, RoundingMode.HALF_UP)
              .multiply(BigDecimal.valueOf(100));
    } else {
      // Handle division by zero if necessary
      compliancePercentage = BigDecimal.ZERO;
    }

    System.out.println("totalOrderedQty2:" + totalOrderedQty);
    System.out.println("totalReceivedQty2:" + totalReceivedQty);
    System.out.println("compliancePercentage2:" + compliancePercentage);

    // Determine quantityComplianceRatingSelect based on compliancePercentage
    Integer quantityComplianceRatingSelect;
    if (compliancePercentage.compareTo(BigDecimal.valueOf(100)) >= 0) {
      quantityComplianceRatingSelect = 3;
    } else if (compliancePercentage.compareTo(BigDecimal.valueOf(80)) >= 0) {
      quantityComplianceRatingSelect = 2;
    } else if (compliancePercentage.compareTo(BigDecimal.valueOf(70)) >= 0) {
      quantityComplianceRatingSelect = 1;
    } else {
      quantityComplianceRatingSelect = 0;
    }
    return quantityComplianceRatingSelect;
  }

  private Integer calculatPriceStabilityRatingSelect(
      List<PurchaseOrderLine> purchaseOrderLinesList, Partner partner) {
    List<Product> products = new ArrayList<>();
    for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLinesList) {
      Product product = purchaseOrderLine.getProduct();
      products.add(product);
    }

    List<BigDecimal> averages = new ArrayList<>();
    for (Product product : products) {
      BigDecimal avg = calculateAvgProduct(product, partner);
      averages.add(avg);
    }

    List<BigDecimal> differences = new ArrayList<>();
    for (int i = 0; i < purchaseOrderLinesList.size(); i++) {
      BigDecimal price = purchaseOrderLinesList.get(i).getPrice();
      BigDecimal avg = averages.get(i);

      BigDecimal difference = price.subtract(avg);
      BigDecimal percentage =
          difference.divide(avg, 4, BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100));

      differences.add(percentage.abs());
    }

    BigDecimal totalDifference = BigDecimal.ZERO;
    for (BigDecimal difference : differences) {
      totalDifference = totalDifference.add(difference);
    }

    // Calculate the average percentage difference
    BigDecimal averageDifference =
        totalDifference.divide(BigDecimal.valueOf(differences.size()), 2, BigDecimal.ROUND_HALF_UP);

    // Determine priceStabilityRatingSelect based on the average percentage difference
    Integer priceStabilityRatingSelect;
    if (averageDifference.compareTo(BigDecimal.ZERO) <= 0) {
      priceStabilityRatingSelect = 3; // Stable prices
    } else if (averageDifference.compareTo(BigDecimal.valueOf(5)) <= 0) {
      priceStabilityRatingSelect = 2; // Moderately stable prices
    } else if (averageDifference.compareTo(BigDecimal.valueOf(10)) <= 0) {
      priceStabilityRatingSelect = 1; // Unstable prices
    } else {
      priceStabilityRatingSelect = 0;
    }

    return priceStabilityRatingSelect;
  }

  private Integer calculatPriceStabilityRatingSelectLastPrice(
      List<PurchaseOrderLine> purchaseOrderLinesList, Partner partner) {
    List<Product> products = new ArrayList<>();
    for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLinesList) {
      Product product = purchaseOrderLine.getProduct();
      products.add(product);
    }

    List<BigDecimal> lastPrices = new ArrayList<>();
    for (Product product : products) {
      BigDecimal lastPrice = getLastPriceProduct(product, partner);
      lastPrices.add(lastPrice);
    }

    List<BigDecimal> differences = new ArrayList<>();
    for (int i = 0; i < purchaseOrderLinesList.size(); i++) {
      BigDecimal price = purchaseOrderLinesList.get(i).getPrice();
      BigDecimal lastPrice = lastPrices.get(i);

      BigDecimal difference = price.subtract(lastPrice);
      BigDecimal percentage =
          difference
              .divide(lastPrice, 4, BigDecimal.ROUND_HALF_UP)
              .multiply(BigDecimal.valueOf(100));

      differences.add(percentage);
    }

    BigDecimal totalDifference = BigDecimal.ZERO;
    for (BigDecimal difference : differences) {
      totalDifference = totalDifference.add(difference);
    }

    // Calculate the average percentage difference
    BigDecimal averageDifference =
        totalDifference.divide(BigDecimal.valueOf(differences.size()), 2, BigDecimal.ROUND_HALF_UP);

    // Determine priceStabilityRatingSelect based on the average percentage difference
    Integer priceStabilityRatingSelect;
    if (averageDifference.compareTo(BigDecimal.ZERO) <= 0) {
      priceStabilityRatingSelect = 3; // Stable prices
    } else if (averageDifference.compareTo(BigDecimal.valueOf(5)) <= 0) {
      priceStabilityRatingSelect = 2; // Moderately stable prices
    } else if (averageDifference.compareTo(BigDecimal.valueOf(10)) <= 0) {
      priceStabilityRatingSelect = 1; // Unstable prices
    } else {
      priceStabilityRatingSelect = 0;
    }

    return priceStabilityRatingSelect;
  }

  private BigDecimal calculateAvgProduct(Product product, Partner partner) {
    // Beans.get(PurchaseOrderRepository.class).find(originId);
    /*List<PurchaseOrderLine> purchaseOrderLines = Beans.get(PurchaseOrderLineRepository.class)
    .all()
    .filter("self.purchaseOrder.supplierPartner = :partner AND self.product = :product")
    .bind("partner", partner)
    .bind("product", product)
    .fetch();*/

    List<PurchaseOrderLine> purchaseOrderLines =
        Beans.get(PurchaseOrderLineRepository.class)
            .all()
            .fetch()
            .stream()
            .filter(
                t ->
                    t.getPurchaseOrder().getSupplierPartner() == partner
                        && t.getProduct() == product)
            .collect(Collectors.toList());

    if (purchaseOrderLines != null && !purchaseOrderLines.isEmpty()) {
      BigDecimal total = BigDecimal.ZERO;
      for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLines) {
        BigDecimal price = purchaseOrderLine.getPrice();
        total = total.add(price);
      }
      BigDecimal avgPrice =
          total.divide(BigDecimal.valueOf(purchaseOrderLines.size()), 2, BigDecimal.ROUND_HALF_UP);
      return avgPrice;
    }
    // Handle the case where there are no purchase order lines for the given product and partner
    return BigDecimal.ZERO;
  }

  private BigDecimal getLastPriceProduct(Product product, Partner partner) {

    List<PurchaseOrderLine> purchaseOrderLines =
        Beans.get(PurchaseOrderLineRepository.class)
            .all()
            .filter("self.purchaseOrder.supplierPartner = :partner AND self.product = :product")
            .bind("partner", partner)
            .bind("product", product)
            .fetch();

    PurchaseOrderLine lastPurchaseOrderLine =
        purchaseOrderLines.isEmpty() ? null : purchaseOrderLines.get(purchaseOrderLines.size() - 1);
    if (lastPurchaseOrderLine != null) {
      BigDecimal price = lastPurchaseOrderLine.getPrice();
      return price;
    } else {
      return BigDecimal.ZERO;
    }
  }
}
