/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2019 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.axelor.apps.account.service.payment.paymentvoucher;

import com.axelor.apps.account.db.Account;
import com.axelor.apps.account.db.AccountConfig;
import com.axelor.apps.account.db.CashRegister;
import com.axelor.apps.account.db.Invoice;
import com.axelor.apps.account.db.Journal;
import com.axelor.apps.account.db.Move;
import com.axelor.apps.account.db.MoveLine;
import com.axelor.apps.account.db.PayVoucherDueElement;
import com.axelor.apps.account.db.PayVoucherElementToPay;
import com.axelor.apps.account.db.PaymentMode;
import com.axelor.apps.account.db.PaymentVoucher;
import com.axelor.apps.account.db.Reconcile;
import com.axelor.apps.account.db.repo.CashRegisterRepository;
import com.axelor.apps.account.db.repo.InvoiceRepository;
import com.axelor.apps.account.db.repo.MoveRepository;
import com.axelor.apps.account.db.repo.PayVoucherElementToPayRepository;
import com.axelor.apps.account.db.repo.PaymentVoucherRepository;
import com.axelor.apps.account.exception.IExceptionMessage;
import com.axelor.apps.account.service.AccountCustomerService;
import com.axelor.apps.account.service.ReconcileService;
import com.axelor.apps.account.service.app.AppAccountService;
import com.axelor.apps.account.service.config.AccountConfigService;
import com.axelor.apps.account.service.move.MoveCancelService;
import com.axelor.apps.account.service.move.MoveLineService;
import com.axelor.apps.account.service.move.MoveService;
import com.axelor.apps.account.service.move.MoveServiceImpl;
import com.axelor.apps.account.service.payment.PaymentModeService;
import com.axelor.apps.account.service.payment.PaymentService;
import com.axelor.apps.base.db.BankDetails;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.Partner;
import com.axelor.auth.AuthUtils;
import com.axelor.exception.AxelorException;
import com.axelor.exception.db.repo.TraceBackRepository;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PaymentVoucherConfirmService {

  private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  protected ReconcileService reconcileService;
  protected MoveLineService moveLineService;
  protected MoveService moveService;
  protected PaymentService paymentService;
  protected PaymentModeService paymentModeService;
  protected PaymentVoucherSequenceService paymentVoucherSequenceService;
  protected PaymentVoucherControlService paymentVoucherControlService;
  protected PaymentVoucherToolService paymentVoucherToolService;
  protected PayVoucherElementToPayRepository payVoucherElementToPayRepo;
  protected PaymentVoucherRepository paymentVoucherRepository;
  protected List<Reconcile> reconciles = new ArrayList<Reconcile>();

  @Inject
  public PaymentVoucherConfirmService(
      ReconcileService reconcileService,
      MoveLineService moveLineService,
      MoveService moveService,
      PaymentService paymentService,
      PaymentModeService paymentModeService,
      PaymentVoucherSequenceService paymentVoucherSequenceService,
      PaymentVoucherControlService paymentVoucherControlService,
      PaymentVoucherToolService paymentVoucherToolService,
      PayVoucherElementToPayRepository payVoucherElementToPayRepo,
      PaymentVoucherRepository paymentVoucherRepository) {

    this.reconcileService = reconcileService;
    this.moveLineService = moveLineService;
    this.moveService = moveService;
    this.paymentService = paymentService;
    this.paymentModeService = paymentModeService;
    this.paymentVoucherSequenceService = paymentVoucherSequenceService;
    this.paymentVoucherControlService = paymentVoucherControlService;
    this.paymentVoucherToolService = paymentVoucherToolService;
    this.payVoucherElementToPayRepo = payVoucherElementToPayRepo;
    this.paymentVoucherRepository = paymentVoucherRepository;
  }

  /**
   * Confirms the payment voucher if the selected lines PiToPay 2nd O2M belongs to different
   * companies -> error I - Payment with an amount If we pay a classical moveLine (invoice, reject
   * ..) -> just create a payment If we pay a schedule 2 payments are created 1st reconciled with
   * the invoice and the second reconciled with the schedule II - Payment with an excess Payment If
   * we pay a moveLine having the same account, we just reconcile If we pay a with different account
   * -> 1- switch money to the good account 2- reconcile then
   *
   * @param paymentVoucher
   */
  @Transactional(rollbackOn = {Exception.class})
  public void confirmPaymentVoucher(PaymentVoucher paymentVoucher, Boolean isConfirmed)
      throws AxelorException {
    log.debug("In confirmPaymentVoucherService ....");
    paymentVoucherSequenceService.setReference(paymentVoucher);

    PaymentMode paymentMode = paymentVoucher.getPaymentMode();
    Company company = paymentVoucher.getCompany();
    BankDetails companyBankDetails = paymentVoucher.getCompanyBankDetails();
    Journal journal;

    if (paymentVoucher.getPaymentMode().getId() == 12
        || paymentVoucher.getPaymentMode().getId() == 23) {
      if (paymentVoucher.getPartner().getAccountingManagementList().size() == 0) {
        throw new AxelorException(
            paymentVoucher,
            TraceBackRepository.CATEGORY_INCONSISTENCY,
            I18n.get("Journal manquant !!"),
            I18n.get(com.axelor.apps.base.exceptions.IExceptionMessage.EXCEPTION));
      } else {
        journal = paymentVoucher.getPartner().getAccountingManagementList().get(0).getJournal();
      }
    } else {
      journal = paymentModeService.getPaymentModeJournal(paymentMode, company, companyBankDetails);
      Account paymentModeAccount =
          paymentModeService.getPaymentModeAccount(paymentMode, company, companyBankDetails);

      paymentVoucherControlService.checkPaymentVoucherField(
          paymentVoucher, company, paymentModeAccount, journal);
    }

    if (paymentVoucher.getRemainingAmount().compareTo(BigDecimal.ZERO) > 0
        && !journal.getExcessPaymentOk()) {
      throw new AxelorException(
          paymentVoucher,
          TraceBackRepository.CATEGORY_INCONSISTENCY,
          I18n.get(IExceptionMessage.PAYMENT_AMOUNT_EXCEEDING),
          I18n.get(com.axelor.apps.base.exceptions.IExceptionMessage.EXCEPTION));
    }

    // TODO VEIRIFER QUE LES ELEMENTS A PAYER NE CONCERNE QU'UNE SEULE DEVISE

    // TODO RECUPERER DEVISE DE LA PREMIERE DETTE
    //		Currency currencyToPay = null;

    AppAccountService appAccountService = Beans.get(AppAccountService.class);

    if (appAccountService.getAppAccount().getPaymentVouchersOnInvoice()
        && paymentVoucher.getPaymentMode().getValidatePaymentByDepositSlipPublication()) {
      waitForDepositSlip(paymentVoucher);
    } else {
      if (!isConfirmed && paymentVoucher.getGeneratedMoveList().size() == 0) {
        createMoveAndConfirm(paymentVoucher);
      }
      if ((paymentVoucher.getPaymentMode().getId() == 12
              || paymentVoucher.getPaymentMode().getId() == 23)
          && isConfirmed) {
        confirmBankMove(paymentVoucher);
      }
    }

    paymentVoucher.setStatusTypeSelect(1); // confirmé
    paymentVoucherSequenceService.setReceiptNo(paymentVoucher, company, journal);
    paymentVoucherRepository.save(paymentVoucher);
  }

  private void waitForDepositSlip(PaymentVoucher paymentVoucher) {
    for (PayVoucherElementToPay payVoucherElementToPay :
        paymentVoucher.getPayVoucherElementToPayList()) {
      Invoice invoice = payVoucherElementToPay.getMoveLine().getMove().getInvoice();
      boolean hasPendingPayments =
          payVoucherElementToPay.getRemainingAmountAfterPayment().signum() <= 0;
      invoice.setHasPendingPayments(hasPendingPayments);
    }

    paymentVoucher.setStatusSelect(PaymentVoucherRepository.STATUS_WAITING_FOR_DEPOSIT_SLIP);
  }

  /**
   * Confirm payment voucher and create move.
   *
   * @param paymentVoucher
   * @throws AxelorException
   */
  @Transactional(rollbackOn = {Exception.class})
  public void createMoveAndConfirm(PaymentVoucher paymentVoucher) throws AxelorException {
    Partner payerPartner = paymentVoucher.getPartner();
    PaymentMode paymentMode = paymentVoucher.getPaymentMode();
    Company company = paymentVoucher.getCompany();
    BankDetails companyBankDetails = paymentVoucher.getCompanyBankDetails();
    Journal journal;
    // LocalDate paymentDate = paymentVoucher.getPaymentDate();
    LocalDate paymentDate = paymentVoucher.getPaymentEmissionDate();

    boolean scheduleToBePaid = false;
    Account paymentModeAccount;

    if (paymentVoucher.getPaymentMode().getId() == 12
        || paymentVoucher.getPaymentMode().getId() == 23) {
      paymentModeAccount =
          paymentVoucher.getPartner().getAccountingManagementList().get(0).getCashAccount();
      journal = paymentVoucher.getPartner().getAccountingManagementList().get(0).getJournal();
    } else {
      journal = paymentModeService.getPaymentModeJournal(paymentMode, company, companyBankDetails);
      paymentModeAccount =
          paymentModeService.getPaymentModeAccount(paymentMode, company, companyBankDetails);
    }

    // If paid by a moveline check if all the lines selected have the same account + company
    // Excess payment
    boolean allRight =
        paymentVoucherControlService.checkIfSameAccount(
            paymentVoucher.getPayVoucherElementToPayList(), paymentVoucher.getMoveLine());
    // Check if allright=true (means companies and accounts in lines are all the same and same as in
    // move line selected for paying
    log.debug("allRight : {}", allRight);

    if (allRight) {
      scheduleToBePaid =
          this.toPayWithExcessPayment(
              paymentVoucher.getPayVoucherElementToPayList(),
              paymentVoucher.getMoveLine(),
              scheduleToBePaid,
              paymentDate);
    }

    if (paymentVoucher.getMoveLine() == null
        || (paymentVoucher.getMoveLine() != null && !allRight)
        || (scheduleToBePaid && !allRight && paymentVoucher.getMoveLine() != null)) {

      // Manage all the cases in the same way. As if a move line (Excess payment) is selected, we
      // cancel it first
      Move move =
          moveService
              .getMoveCreateService()
              .createMoveWithPaymentVoucher(
                  journal,
                  company,
                  paymentVoucher,
                  payerPartner,
                  paymentDate,
                  paymentMode,
                  MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);

      move.setPaymentVoucher(paymentVoucher);

      paymentVoucher.setGeneratedMove(move);

      // confirmed by
      paymentVoucher.setConfirmedByUser(AuthUtils.getUser());
      paymentVoucher.setConfirmationDate(LocalDate.now());

      // Create move lines for payment lines
      BigDecimal paidLineTotal = BigDecimal.ZERO;
      int moveLineNo = 1;

      boolean isDebitToPay = paymentVoucherToolService.isDebitToPay(paymentVoucher);

      List<? extends PayVoucherElementToPay> payVoucherElementToPayList =
          this.getPayVoucherElementToPayList(paymentVoucher);

      MoveLine moveLineInvoices = null;

      if (paymentVoucher.getOperationTypeSelect() == 6) {

        moveLineInvoices =
            moveLineService.createMoveLine(
                move,
                payerPartner,
                payerPartner.getAccountingSituationList().get(0).getCustomerAccount(),
                paymentVoucher.getPaidAmount(),
                !isDebitToPay,
                paymentDate,
                moveLineNo++,
                paymentVoucher.getRef(),
                null);
        paidLineTotal = paymentVoucher.getPaidAmount();

        move.getMoveLineList().add(moveLineInvoices);

      } else {

        for (PayVoucherElementToPay payVoucherElementToPay : payVoucherElementToPayList) {
          MoveLine moveLineToPay = payVoucherElementToPay.getMoveLine();
          log.debug("PV moveLineToPay debit : {}", moveLineToPay.getDebit());
          log.debug("PV moveLineToPay credit : {}", moveLineToPay.getCredit());
          log.debug("PV moveLineToPay amountPaid : {}", moveLineToPay.getAmountPaid());

          BigDecimal amountToPay = payVoucherElementToPay.getAmountToPayCurrency();
          log.debug(">>>> PV amountToPay : {}", amountToPay);

          Invoice invoice = payVoucherElementToPay.getMoveLine().getMove().getInvoice();
          isDebitToPay = paymentVoucherToolService.isDebitToPay(invoice);

          boolean isRefundInvoice = false;
          if (invoice.getOperationTypeSelect() == InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND) {
            isRefundInvoice = true;
          }

          log.debug("heyyyyyyyyyyyyyyyyy : {}", isRefundInvoice);

          if (amountToPay.compareTo(BigDecimal.ZERO) > 0 || isRefundInvoice) {

            if (isRefundInvoice
            // || paymentVoucher.getOperationTypeSelect()
            //     == PaymentVoucherRepository.OPERATION_TYPE_CLIENT_SALE_CLIENT_REFUND
            // || paymentVoucher.getOperationTypeSelect()
            //     == PaymentVoucherRepository.OPERATION_TYPE_SUPPLIER_PURCHASE_SUPPLIER_REFUND
            ) {
              amountToPay = payVoucherElementToPay.getTotalAmount();
              log.debug(
                  "**** getInvoiceId {} *** isDebitToPay {} **** amountToPay {}",
                  invoice.getInvoiceId(),
                  isDebitToPay,
                  amountToPay);
            }

            paidLineTotal = paidLineTotal.add(amountToPay);

            log.debug("amountToPay >>>>>>>>>>> {}", amountToPay);

            this.payMoveLine(
                move,
                moveLineNo++,
                payerPartner,
                moveLineToPay,
                amountToPay,
                amountToPay,
                // isRefundInvoice ? amountToPay : payVoucherElementToPay.getAmountToPayCurrency(),
                payVoucherElementToPay,
                isDebitToPay,
                paymentDate);
          }
        }
      }
      // Create move line for the payment amount
      MoveLine moveLine = null;
      isDebitToPay = paymentVoucherToolService.isDebitToPay(paymentVoucher);

      // cancelling the moveLine (excess payment) by creating the balance of all the payments
      // on the same account as the moveLine (excess payment)
      // in the else case we create a classical balance on the bank account of the payment mode
      if (paymentVoucher.getMoveLine() != null) {
        moveLine =
            moveLineService.createMoveLine(
                move,
                paymentVoucher.getPartner(),
                paymentVoucher.getMoveLine().getAccount(),
                paymentVoucher.getPaidAmount(),
                isDebitToPay,
                paymentDate,
                moveLineNo++,
                paymentVoucher.getRef(),
                null);

        //   Reconcile reconcile =
        //     reconcileService.createReconcile(
        //         moveLine, paymentVoucher.getMoveLine(), moveLine.getDebit(), !isDebitToPay);
        // if (reconcile != null) {
        //     reconcileService.confirmReconcile(reconcile, true);
        // }
      } else {
        BigDecimal paidAmount = BigDecimal.ZERO;

        // if(paymentVoucher.getOperationTypeSelect() == 6){
        //   paidAmount = processSaleAndRefund(payVoucherElementToPayList);
        // }else{
        paidAmount = paymentVoucher.getPaidAmount();
        // }

        moveLine =
            moveLineService.createMoveLine(
                move,
                payerPartner,
                paymentModeAccount,
                paidAmount,
                isDebitToPay,
                paymentDate,
                moveLineNo++,
                paymentVoucher.getRef(),
                null);
      }
      move.getMoveLineList().add(moveLine);

      // Check if the paid amount is > paid lines total
      // Then Use Excess payment on old invoices / moveLines
      if (paymentVoucher.getPaidAmount().compareTo(paidLineTotal) > 0
          || paymentVoucher
                  .getPaidAmount()
                  .compareTo(processSaleAndRefundTotal(paymentVoucher).get("total"))
              > 0) {
        BigDecimal remainingPaidAmount = paymentVoucher.getRemainingAmount();

        // TODO rajouter le process d'imputation automatique
        //              if(paymentVoucher.getHasAutoInput())  {
        //
        //                  List<MoveLine> debitMoveLines =
        // Lists.newArrayList(pas.getDebitLinesToPay(contractLine,
        // paymentVoucher.getPaymentScheduleToPay()));
        //                  pas.createExcessPaymentWithAmount(debitMoveLines, remainingPaidAmount,
        // move, moveLineNo,
        //                          paymentVoucher.getPayerPartner(), company, contractLine, null,
        // paymentDate, updateCustomerAccount);
        //              }
        //              else  {

        Account partnerAccount =
            Beans.get(AccountCustomerService.class)
                .getPartnerAccount(
                    payerPartner, company, paymentVoucherToolService.isPurchase(paymentVoucher));

        moveLine =
            moveLineService.createMoveLine(
                move,
                paymentVoucher.getPartner(),
                partnerAccount,
                remainingPaidAmount,
                !isDebitToPay,
                paymentDate,
                moveLineNo++,
                paymentVoucher.getRef(),
                null);

        move.getMoveLineList().add(moveLine);

        log.debug("******************** remainingPaidAmount {}", remainingPaidAmount);
        log.debug("******************** isDebitToPay confirm voucher{}", moveLine.toString());
        log.debug("******************** isDebitToPay confirm voucher{}", moveLine.toString());

        if (isDebitToPay) {
          reconcileService.balanceCredit(moveLine);
        }
      }
      moveService.getMoveValidateService().validate(move);
      paymentVoucher.setGeneratedMove(move);
      paymentVoucher.addGeneratedMoveListItem(move);
    }
    // paymentVoucher.setStatusSelect(PaymentVoucherRepository.STATUS_CONFIRMED);

    Beans.get(PaymentVoucherLoadService.class).updateVoucher(paymentVoucher);

    deleteUnPaidLines(paymentVoucher);
  }

  @Transactional(rollbackOn = {Exception.class})
  public void confirmBankMove(PaymentVoucher paymentVoucher) throws AxelorException {
    Partner payerPartner = paymentVoucher.getPartner();
    PaymentMode paymentMode = paymentVoucher.getPaymentMode();
    Company company = paymentVoucher.getCompany();
    BankDetails companyBankDetails = paymentVoucher.getCompanyBankDetails();
    Journal journal =
        paymentModeService.getPaymentModeJournal(paymentMode, company, companyBankDetails);
    // LocalDate paymentDate = paymentVoucher.getPaymentDate();
    LocalDate paymentDate = paymentVoucher.getPaymentEmissionDate();
    LocalDate paymentDateEchance = paymentVoucher.getDueDate();

    if (paymentVoucher.getSort() == 3) {
      paymentDateEchance = paymentVoucher.getCreditDate();
    }

    boolean scheduleToBePaid = false;
    Account paymentModeAccount =
        paymentModeService.getPaymentModeAccount(paymentMode, company, companyBankDetails);

    Move move =
        moveService
            .getMoveCreateService()
            .createMoveWithPaymentVoucher(
                journal,
                company,
                paymentVoucher,
                payerPartner,
                paymentDateEchance,
                paymentMode,
                MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);

    move.setPaymentVoucher(paymentVoucher);

    boolean isDebitToPay = paymentVoucherToolService.isDebitToPay(paymentVoucher);

    List<? extends PayVoucherElementToPay> payVoucherElementToPayList =
        this.getPayVoucherElementToPayList(paymentVoucher);

    BigDecimal paidAmount = paymentVoucher.getPaidAmount();

    int moveLineNo = 1;

    MoveLine moveLine2 =
        moveLineService.createMoveLine(
            move,
            payerPartner,
            paymentVoucher.getPartner().getAccountingManagementList().get(0).getCashAccount(),
            paidAmount,
            !isDebitToPay,
            paymentDateEchance,
            moveLineNo++,
            paymentVoucher.getRef(),
            null);

    MoveLine moveLine =
        moveLineService.createMoveLine(
            move,
            payerPartner,
            paymentModeAccount,
            paidAmount,
            isDebitToPay,
            paymentDateEchance,
            moveLineNo++,
            paymentVoucher.getRef(),
            null);

    move.getMoveLineList().add(moveLine);
    move.getMoveLineList().add(moveLine2);

    moveService.getMoveValidateService().validate(move);
    paymentVoucher.setGeneratedMove(move);
    paymentVoucher.addGeneratedMoveListItem(move);

    if (isDebitToPay) {
      reconcileService.balanceCredit(moveLine);
    }
    Beans.get(PaymentVoucherLoadService.class).updateVoucher(paymentVoucher);
  }

  @Transactional(rollbackOn = {Exception.class})
  public void confirmOtherPayment(PaymentVoucher paymentVoucher) throws AxelorException {
    PaymentMode paymentMode = paymentVoucher.getPaymentMode();
    Company company = paymentVoucher.getCompany();
    BankDetails companyBankDetails = paymentVoucher.getCompanyBankDetails();
    Journal journal =
        paymentModeService.getPaymentModeJournal(paymentMode, company, companyBankDetails);
    // LocalDate paymentDate = paymentVoucher.getPaymentDate();
    LocalDate paymentDate = paymentVoucher.getPaymentEmissionDate();
    LocalDate paymentDateEchance = paymentVoucher.getDueDate();

    if (paymentVoucher.getSort() == 3) {
      paymentDateEchance = paymentVoucher.getCreditDate();
    }

    boolean scheduleToBePaid = false;
    Account paymentModeAccount =
        paymentModeService.getPaymentModeAccount(paymentMode, company, companyBankDetails);

    Move move =
        moveService
            .getMoveCreateService()
            .createMoveWithPaymentVoucher(
                journal,
                company,
                paymentVoucher,
                null,
                paymentDate,
                paymentMode,
                MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);
                
    move.setPaymentVoucher(paymentVoucher);

    List<? extends PayVoucherElementToPay> payVoucherElementToPayList =
        this.getPayVoucherElementToPayList(paymentVoucher);

    Boolean isDebitToPay = false;

    Account accountToPay = null;

    if (paymentModeAccount.getIsOriginRequired()) {
      MoveLine moveLineToPay = payVoucherElementToPayList.get(0).getMoveLine();

      if (moveLineToPay.getCredit().compareTo(BigDecimal.ZERO) > 0) {
        isDebitToPay = true;
      }
      accountToPay = moveLineToPay.getAccount();
    } else {
      accountToPay =
          paymentVoucher
              .getPartner()
              .getAccountingSituationList()
              .get(0)
              .getPartnerPaymentAccount();
    }

    BigDecimal paidAmount = paymentVoucher.getPaidAmount();

    int moveLineNo = 1;

    MoveLine moveLine2 =
        moveLineService.createMoveLine(
            move,
            null,
            accountToPay,
            paidAmount,
            !isDebitToPay,
            paymentDateEchance,
            moveLineNo++,
            paymentVoucher.getRef(),
            null);

    MoveLine moveLine =
        moveLineService.createMoveLine(
            move,
            null,
            paymentModeAccount,
            paidAmount,
            isDebitToPay,
            paymentDateEchance,
            moveLineNo++,
            paymentVoucher.getRef(),
            null);

    move.getMoveLineList().add(moveLine);
    move.getMoveLineList().add(moveLine2);

    moveService.getMoveValidateService().validate(move);
    paymentVoucher.setGeneratedMove(move);
    paymentVoucher.addGeneratedMoveListItem(move);
    paymentVoucher.setConfirmationDate(LocalDate.now());
    paymentVoucher.setConfirmedByUser(AuthUtils.getUser());

    if (isDebitToPay) {
      reconcileService.balanceCredit(moveLine);
    }
    if(paymentVoucher.getPartner().getId() == 2040L){
      this.updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay,paidAmount);
    }
    // Beans.get(PaymentVoucherLoadService.class).updateVoucher(paymentVoucher);
  }

  public void deleteUnPaidLines(PaymentVoucher paymentVoucher) {

    if (paymentVoucher.getPayVoucherElementToPayList() == null) {
      return;
    }

    paymentVoucher.getPayVoucherDueElementList().clear();

    List<PayVoucherElementToPay> payVoucherElementToPayToRemove = new ArrayList<>();

    for (PayVoucherElementToPay payVoucherElementToPay :
        paymentVoucher.getPayVoucherElementToPayList()) {

      if (payVoucherElementToPay.getAmountToPay().compareTo(BigDecimal.ZERO) == 0
          && payVoucherElementToPay.getMoveLineGenerated() == null) {

        payVoucherElementToPayToRemove.add(payVoucherElementToPay);
      }
    }

    paymentVoucher.getPayVoucherElementToPayList().removeAll(payVoucherElementToPayToRemove);
  }

  /**
   * Récupérer les éléments à payer dans le bon ordre
   *
   * @return
   */
  public List<? extends PayVoucherElementToPay> getPayVoucherElementToPayList(
      PaymentVoucher paymentVoucher) {

    return payVoucherElementToPayRepo
        .all()
        .filter("self.paymentVoucher = ?1 ORDER by self.sequence ASC", paymentVoucher)
        .fetch();
  }

  /**
   * If paid by a moveline check if all the lines selected have the same account + company Excess
   * payment Check if allright=true (means companies and accounts in lines are all the same and same
   * as in move line selected for paying
   *
   * @param payVoucherElementToPayList Liste des paiement a réaliser
   * @param creditMoveLine Le trop-perçu
   * @param scheduleToBePaid
   * @return Une échéance doit-elle être payée?
   * @throws AxelorException
   */
  public boolean toPayWithExcessPayment(
      List<PayVoucherElementToPay> payVoucherElementToPayList,
      MoveLine creditMoveLine,
      boolean scheduleToBePaid,
      LocalDate paymentDate)
      throws AxelorException {
    boolean scheduleToBePaid2 = scheduleToBePaid;

    List<MoveLine> debitMoveLines = new ArrayList<MoveLine>();
    for (PayVoucherElementToPay payVoucherElementToPay : payVoucherElementToPayList) {

      debitMoveLines.add(payVoucherElementToPay.getMoveLine());
    }
    List<MoveLine> creditMoveLines = new ArrayList<MoveLine>();
    creditMoveLines.add(creditMoveLine);
    paymentService.useExcessPaymentOnMoveLines(debitMoveLines, creditMoveLines);
    return scheduleToBePaid2;
  }

  /**
   * @param paymentMove
   * @param moveLineSeq
   * @param payerPartner
   * @param moveLineToPay
   * @param amountToPay
   * @param payVoucherElementToPay
   * @return
   * @throws AxelorException
   */
  public MoveLine payMoveLine(
      Move paymentMove,
      int moveLineSeq,
      Partner payerPartner,
      MoveLine moveLineToPay,
      BigDecimal amountToPay,
      BigDecimal amountToPayReel,
      PayVoucherElementToPay payVoucherElementToPay,
      boolean isDebitToPay,
      LocalDate paymentDate)
      throws AxelorException {
    String invoiceName = "";
    if (moveLineToPay.getMove().getInvoice() != null) {
      invoiceName = moveLineToPay.getMove().getInvoice().getInvoiceId();
    } else {
      invoiceName = payVoucherElementToPay.getPaymentVoucher().getRef();
    }
    MoveLine moveLine =
        moveLineService.createMoveLine(
            paymentMove,
            payerPartner,
            moveLineToPay.getAccount(),
            amountToPay,
            !isDebitToPay,
            paymentDate,
            moveLineSeq,
            invoiceName,
            null);

    paymentMove.addMoveLineListItem(moveLine);
    payVoucherElementToPay.setMoveLineGenerated(moveLine);

    BigDecimal amountInCompanyCurrency = moveLine.getDebit().add(moveLine.getCredit());

    // Reconcile reconcile =
    //     reconcileService.createReconcile(moveLineToPay, moveLine, amountToPayReel, true);
    // reconciles.add(reconcile);
    // if (reconcile != null) {
    //   log.debug("Reconcile : : : {}", reconcile);
    //   reconcileService.confirmPaymentVoucherReconcile(reconcile, true);
    // // }
    return moveLine;
  }

  public Map<String, BigDecimal> processSaleAndRefund(PaymentVoucher paymentVoucher) {
    List<? extends PayVoucherDueElement> payVoucherElementToPayList =
        paymentVoucher.getPayVoucherDueElementList();
    BigDecimal total = BigDecimal.ZERO;
    BigDecimal remaining = BigDecimal.ZERO;
    Map<String, BigDecimal> tot = new HashMap<>();

    for (PayVoucherDueElement dueElement : payVoucherElementToPayList) {
      total =
          total.add(dueElement.getMoveLine().getDebit().add(dueElement.getMoveLine().getCredit()));
    }
    if (paymentVoucher.getPaidAmount().compareTo(total) > 0) {
      remaining = paymentVoucher.getPaidAmount().abs().subtract(total).abs();
    }
    tot.put("total", total.abs());
    tot.put("remaining", remaining);
    return tot;
  }

  public Map<String, BigDecimal> processSaleAndRefundTotal(PaymentVoucher paymentVoucher) {
    List<? extends PayVoucherElementToPay> payVoucherElementToPayList =
        paymentVoucher.getPayVoucherElementToPayList();
    BigDecimal total = BigDecimal.ZERO;
    BigDecimal remaining = BigDecimal.ZERO;
    Map<String, BigDecimal> tot = new HashMap<>();

    for (PayVoucherElementToPay elementToPay : payVoucherElementToPayList) {
      total =
          total.add(
              elementToPay
                  .getMoveLine()
                  .getDebit()
                  .subtract(elementToPay.getMoveLine().getCredit()));
    }
    BigDecimal absTotal = total.abs();
    if (paymentVoucher.getPaidAmount().compareTo(total) > 0) {
      remaining = paymentVoucher.getPaidAmount().subtract(absTotal).abs();
    } else {
      remaining = BigDecimal.ZERO;
    }
    tot.put("total", absTotal);
    tot.put("remaining", remaining);
    return tot;
  }

  @Transactional(rollbackOn = {Exception.class})
  public PaymentVoucher copyPaymentVoucher(PaymentVoucher paymentVoucher) throws AxelorException {

    PaymentVoucher paymentVoucher2 =
        Beans.get(PaymentVoucherRepository.class).copy(paymentVoucher, false);
    paymentVoucher2.setGeneratedMove(null);
    paymentVoucher2.setGeneratedMoveList(null);
    paymentVoucher2.setOriginPaymentVoucher(paymentVoucher);
    paymentVoucher2.setRequestDate(null);
    paymentVoucher2.setRequestedByUser(null);
    paymentVoucher2.setAcceptanceDate(null);
    paymentVoucher2.setAcceptedByUser(null);
    paymentVoucher2.setConfirmationDate(null);
    paymentVoucher2.setConfirmedByUser(null);
    paymentVoucher2.setConfirmedByUser(null);
    paymentVoucher2.setRejectedDate(null);
    paymentVoucher2.setRejectedByUser(null);
    paymentVoucher2.setRejectionRaison(null);

    for (PayVoucherElementToPay elementToPay : paymentVoucher.getPayVoucherElementToPayList()) {
      PayVoucherElementToPay elementToPayCopy =
          Beans.get(PayVoucherElementToPayRepository.class).copy(elementToPay, false);
      paymentVoucher2.addPayVoucherElementToPayListItem(elementToPayCopy);
    }

    return paymentVoucherRepository.save(paymentVoucher2);
  }

  @Transactional(rollbackOn = {Exception.class})
  public PaymentVoucher rejectPaymentVoucher(PaymentVoucher paymentVoucher, String cancelRaison)
      throws AxelorException {

    paymentVoucher.setStatusSelect(PaymentVoucherRepository.STATUS_REJECTED);
    paymentVoucher.setRejectionRaison(cancelRaison);
    paymentVoucher.setRejectedDate(LocalDate.now());
    paymentVoucher.setRejectedByUser(AuthUtils.getUser());
    paymentVoucher.setRejectedInstanceSelect(1);

    // for (Move move : paymentVoucher.getGeneratedMoveList()) {
    //   for (MoveLine moveLine : move.getMoveLineList()) {
    //     ReconcileGroup reconcileGroup = moveLine.getReconcileGroup();
    //     if (reconcileGroup != null) {
    //       Beans.get(ReconcileGroupServiceImpl.class).unletter(reconcileGroup);
    //       log.debug("Unlettering ::: {} ******", reconcileGroup);
    //     }
    //   }
    //   // Move move = elementToPay.getMoveLine().getMove();
    //   // for (InvoicePayment  invoicePayment : move.getInvoice().getInvoicePaymentList()) {
    //   //   Beans.get(InvoicePaymentCancelService.class).cancel(invoicePayment);
    //   // }

    // }

    PaymentVoucher paymentVoucher2 =
        Beans.get(PaymentVoucherConfirmService.class).copyPaymentVoucher(paymentVoucher);

    for (Move move : paymentVoucher.getGeneratedMoveList()) {
      log.debug("Rejecting move : : : {} **********", move);

      try {
        Move reverseMove =
            Beans.get(MoveServiceImpl.class)
                .generateReverse(move, false, false, true, LocalDate.now());
        paymentVoucher2.addGeneratedMoveListItem(reverseMove);
        // moveCancelService.cancel(move);
      } catch (AxelorException e) {
        log.debug("Error : : : {}", e);
      }
    }

    paymentVoucherRepository.save(paymentVoucher2);
    return paymentVoucher2;
    // Beans.get(InvoicePaymentRepository.class).all().filter("self.invoice = ?1", null);

  }

  @Transactional(rollbackOn = {Exception.class})
  public Move confirmMultipleVoucher(
      List<PaymentVoucher> voucherList, List<PayVoucherElementToPay> elementToPays, boolean b)
      throws AxelorException {
    if (voucherList != null && voucherList.size() > 0) {
      if (!samePaymentVoucherSelect(voucherList) || voucherAlreadyInMove(voucherList)) {
        throw new AxelorException(
            voucherList.get(0),
            TraceBackRepository.CATEGORY_INCONSISTENCY,
            "Not the same operation type");
      }
      BigDecimal totalAmount = BigDecimal.ZERO;
      totalAmount =
          voucherList
              .stream()
              .map(PaymentVoucher::getPaidAmount)
              .reduce(BigDecimal.ZERO, BigDecimal::add);

      PaymentVoucher paymentVoucher = voucherList.get(0);
      Partner payerPartner = paymentVoucher.getPartner();
      PaymentMode paymentMode = paymentVoucher.getPaymentMode();
      Company company = paymentVoucher.getCompany();
      BankDetails companyBankDetails = paymentVoucher.getCompanyBankDetails();
      Journal journal =
          paymentModeService.getPaymentModeJournal(paymentMode, company, companyBankDetails);
      LocalDate paymentDate = paymentVoucher.getPaymentEmissionDate();

      Account paymentModeAccount =
          paymentModeService.getPaymentModeAccount(paymentMode, company, companyBankDetails);

      if (paymentVoucher.getPaymentMode().getId() == 12
          || paymentVoucher.getPaymentMode().getId() == 23) {
        paymentModeAccount =
            paymentVoucher.getPartner().getAccountingManagementList().get(0).getCashAccount();
        journal = paymentVoucher.getPartner().getAccountingManagementList().get(0).getJournal();
      }

      Move move =
          moveService
              .getMoveCreateService()
              .createMoveWithPaymentVoucher(
                  journal,
                  company,
                  paymentVoucher,
                  payerPartner,
                  paymentDate,
                  paymentMode,
                  MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);

      // move.addPaymentVoucherListItem(paymentVoucher);
      // paymentVoucher.setGeneratedMove(move);
      paymentVoucher.setConfirmedByUser(AuthUtils.getUser());
      paymentVoucher.setConfirmationDate(LocalDate.now());

      boolean isDebitToPay = paymentVoucherToolService.isDebitToPay(paymentVoucher);
      int moveLineNo = 0;

      MoveLine moveLineAll =
          moveLineService.createMoveLine(
              move,
              payerPartner,
              paymentModeAccount,
              totalAmount,
              isDebitToPay,
              paymentDate,
              moveLineNo++,
              paymentVoucher.getRef(),
              null);
      move.getMoveLineList().add(moveLineAll);

      Set<Long> paymentVoucherIds = new HashSet<Long>();

      for (PayVoucherElementToPay voucherElementToPay : elementToPays) {
        MoveLine line =
            moveLineService.createMoveLine(
                move,
                payerPartner,
                voucherElementToPay.getMoveLine().getAccount(),
                voucherElementToPay.getAmountToPay(),
                !isDebitToPay,
                paymentDate,
                moveLineNo++,
                voucherElementToPay.getPaymentVoucher().getRef(),
                null);
        move.addMoveLineListItem(line);
        paymentVoucherIds.add(voucherElementToPay.getPaymentVoucher().getId());
      }
      for (Long voucherId : paymentVoucherIds) {
        PaymentVoucher voucher = Beans.get(PaymentVoucherRepository.class).find(voucherId);
        System.out.println(":::::::::::::::::::>");
        System.out.println(paymentVoucherIds);
        System.out.println(voucher.getGeneratedMoveList());
        System.out.println(":::::::::::::::::::>");
        move.addPaymentVoucherListItem(voucher);
        // voucher.addGeneratedMoveListItem(move);
        voucher.setConfirmedByUser(AuthUtils.getUser());
        voucher.setConfirmationDate(LocalDate.now());
        paymentVoucher.setStatusSelect(PaymentVoucherRepository.STATUS_CONFIRMED);
      }
      return move;
    }
    return null;
  }

  public boolean samePaymentVoucherSelect(List<PaymentVoucher> voucherList) {
    Integer operationTypeSelect = voucherList.get(0).getOperationTypeSelect();
    BankDetails bankDetails = voucherList.get(0).getCompanyBankDetails();
    Partner partner = voucherList.get(0).getPartner();
    for (int index = 1; index < voucherList.size(); index++) {
      PaymentVoucher paymentVoucher = voucherList.get(index);
      if (paymentVoucher.getOperationTypeSelect() != operationTypeSelect
          || paymentVoucher.getPartner() != partner
          || paymentVoucher.getCompanyBankDetails() != bankDetails) {
        return false;
      }
    }
    return true;
  }

  public boolean voucherAlreadyInMove(List<PaymentVoucher> voucherList) {
    if (voucherList != null) {
      for (PaymentVoucher voucher : voucherList) {
        if (voucher.getGeneratedMoveList().size() > 0) {
          return true;
        }
      }
    }
    return false;
  }

  // Cash mvt
  /**
   * Confirm cash payment voucher and create move.
   *
   * @param paymentVoucher
   * @throws AxelorException
   */
  @Transactional(rollbackOn = {Exception.class})
  public void confirmCashPayment(PaymentVoucher paymentVoucher) throws AxelorException {
    if(paymentVoucher.getGeneratedMoveList().size() > 0){
        throw new AxelorException(
          TraceBackRepository.CATEGORY_INCONSISTENCY,
          "Already ventilated.");
    }
    PaymentMode paymentMode = paymentVoucher.getPaymentMode();
    Company company = paymentVoucher.getCompany();
    AccountConfig accountConfig = Beans.get(AccountConfigService.class).getAccountConfig(company);

    Journal journal = accountConfig.getCashJournal();
    // LocalDate paymentDate = paymentVoucher.getPaymentDate();
    LocalDate paymentDate = paymentVoucher.getPaymentEmissionDate();

    Account paymentModeAccount =
        paymentVoucher.getCashAccountConfig().getAccount();

    Move move =
        moveService
            .getMoveCreateService()
            .createMoveWithPaymentVoucher(
                journal,
                company,
                paymentVoucher,
                null,
                paymentDate,
                paymentMode,
                MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);

    move.setPaymentVoucher(paymentVoucher);

    Boolean isDebitToPay = false;

    if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
      isDebitToPay = true;
    }

    Account accountToPay = accountConfig.getCashAccount();

    BigDecimal paidAmount = paymentVoucher.getPaidAmount();

    int moveLineNo = 1;

    MoveLine moveLine2 =
        moveLineService.createMoveLine(
            move,
            null,
            accountToPay,
            paidAmount,
            !isDebitToPay,
            paymentDate,
            moveLineNo++,
            paymentVoucher.getRef(),
            paymentVoucher.getLabel());

    MoveLine moveLine =
        moveLineService.createMoveLine(
            move,
            null,
            paymentModeAccount,
            paidAmount,
            isDebitToPay,
            paymentDate,
            moveLineNo++,
            paymentVoucher.getRef(),
            paymentVoucher.getLabel());

    move.getMoveLineList().add(moveLine);
    move.getMoveLineList().add(moveLine2);

    String label = paymentVoucher.getLabel() != null ? paymentVoucher.getLabel() : "";
    String fullName = (paymentVoucher.getRecipientPartner() != null && paymentVoucher.getRecipientPartner().getFullName() != null) 
                      ? paymentVoucher.getRecipientPartner().getFullName() 
                      : "";

    String description = label +" "+ fullName;

    moveLine.setDescription(description);
    moveLine2.setDescription(description);

    moveService.getMoveValidateService().validate(move);
    paymentVoucher.setGeneratedMove(move);
    paymentVoucher.addGeneratedMoveListItem(move);
    
    // confirmed is ventilation equivalent
    paymentVoucher.setConfirmedByUser(AuthUtils.getUser());
    paymentVoucher.setConfirmationDate(LocalDate.now());

    if (isDebitToPay) {
      reconcileService.balanceCredit(moveLine);
    }
    // updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay,paidAmount);
    // Beans.get(PaymentVoucherLoadService.class).updateVoucher(paymentVoucher);
  }

  @Transactional(rollbackOn = {Exception.class})
  public void updateCashRegisterTheoricalSolde(PaymentVoucher paymentVoucher,Boolean isDebitCredit,BigDecimal amount) {
    System.out.println("updateCashRegisterTheoricalSolde : : : " + paymentVoucher.getCashAccountConfig());
    if (paymentVoucher.getCashAccountConfig() != null || paymentVoucher.getPartner() != null) {
      CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
      CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
      BigDecimal theoreticalSolde = cashRegister.getTheoricalSolde();
      if (isDebitCredit) {
        theoreticalSolde = theoreticalSolde.subtract(amount);
      } else {
        theoreticalSolde = theoreticalSolde.add(amount);
      }
      System.out.println("theoreticalSolde : : : " + theoreticalSolde);
      cashRegister.setTheoricalSolde(theoreticalSolde);
      Beans.get(CashRegisterRepository.class).save(cashRegister);
    }
  }

  @Transactional(rollbackOn = {Exception.class})
  public void validateCashPaymentVoucher(PaymentVoucher paymentVoucher) throws AxelorException {
    if (paymentVoucher.getCashAccountConfig() == null) {
      throw new AxelorException(
          TraceBackRepository.CATEGORY_INCONSISTENCY,
          "Cash account config is not set for the payment voucher.");
    }
    if (paymentVoucher.getCashAccountConfig().getAccount() == null) {
      throw new AxelorException(
          TraceBackRepository.CATEGORY_INCONSISTENCY,
          "Cash account is not set for the payment voucher.");
        }
      CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
      CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
      
      BigDecimal solde = cashRegister.getTheoricalSolde();
      if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
        solde = solde.subtract(paymentVoucher.getPaidAmount());
      }else{
        solde = solde.add(paymentVoucher.getPaidAmount());
      }

      paymentVoucher.setInitialTheoricalSolde(cashRegister.getTheoricalSolde()); 
      paymentVoucher.setTheoricalSolde(solde); 
      paymentVoucher.setCashStatusSelect(PaymentVoucherRepository.STATUS_WAITING_FOR_DEPOSIT_SLIP);
      paymentVoucher.setApprovalDate(LocalDate.now());
      paymentVoucher.setApprovedByUser(AuthUtils.getUser());
          Boolean isDebitToPay = false;

    if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
      isDebitToPay = true;
    }
      updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay,paymentVoucher.getPaidAmount());
  }

  @Transactional(rollbackOn = {Exception.class})
  public void cancelCashPaymentVoucher(PaymentVoucher paymentVoucher) throws AxelorException {
        if (paymentVoucher.getCashAccountConfig() == null) {
              throw new AxelorException(
                  TraceBackRepository.CATEGORY_INCONSISTENCY,
                  "Cash account config is not set for the payment voucher.");
        }
        if (paymentVoucher.getCashAccountConfig().getAccount() == null) {
          throw new AxelorException(
              TraceBackRepository.CATEGORY_INCONSISTENCY,
              "Cash account is not set for the payment voucher.");
        }
        CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
        CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
        
        BigDecimal solde = cashRegister.getTheoricalSolde();
        if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 2) {
          solde = solde.subtract(paymentVoucher.getPaidAmount());
        }else{
          solde = solde.add(paymentVoucher.getPaidAmount());
        }

        paymentVoucher.setInitialTheoricalSolde(cashRegister.getTheoricalSolde()); 
        paymentVoucher.setTheoricalSolde(solde); 
        paymentVoucher.setCashStatusSelect(4);
        Boolean isDebitToPay = false;

        if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
          isDebitToPay = true;
        }
        // Beans.get(MoveServiceImpl.class).
        updateCashRegisterTheoricalSolde(paymentVoucher, !isDebitToPay,paymentVoucher.getPaidAmount());
        for (Move move : paymentVoucher.getGeneratedMoveList()) {
              Beans.get(MoveCancelService.class).cancel(move);
        }
  }


}
