/*
 * 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.web;

import com.axelor.apps.ReportFactory;
import com.axelor.apps.account.db.*;
import com.axelor.apps.account.db.repo.InvoiceRepository;
import com.axelor.apps.account.db.repo.PaymentVoucherRepository;
import com.axelor.apps.account.report.IReport;
import com.axelor.apps.account.service.payment.PaymentModeService;
import com.axelor.apps.account.service.payment.paymentvoucher.PaymentVoucherConfirmService;
import com.axelor.apps.account.service.payment.paymentvoucher.PaymentVoucherLoadService;
import com.axelor.apps.account.service.payment.paymentvoucher.PaymentVoucherSequenceService;
import com.axelor.apps.base.db.BankDetails;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.Partner;
import com.axelor.apps.base.db.Wizard;
import com.axelor.apps.base.db.repo.PartnerRepository;
import com.axelor.apps.base.service.BankDetailsService;
import com.axelor.apps.base.service.administration.SequenceService;
import com.axelor.db.mapper.Mapper;
import com.axelor.exception.AxelorException;
import com.axelor.exception.service.TraceBackService;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.axelor.meta.schema.actions.ActionView;
import com.axelor.meta.schema.actions.ActionView.ActionViewBuilder;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.common.base.Strings;
import com.google.inject.Singleton;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class PaymentVoucherController {

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

  // Called on onSave event
  public void paymentVoucherSetNum(ActionRequest request, ActionResponse response)
      throws AxelorException {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);

    if (Strings.isNullOrEmpty(paymentVoucher.getRef())) {
      if (paymentVoucher.getOperationTypeSelect() == 9) {
        response.setValue(
            "ref",
            Beans.get(SequenceService.class)
                .getSequenceNumber("CashSequence", paymentVoucher.getCompany()));
      } else {
        response.setValue(
            "ref", Beans.get(PaymentVoucherSequenceService.class).getReference(paymentVoucher));
      }
    }
  }

  // Loading move lines of the selected partner (1st O2M)
  public void loadMoveLines(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherLoadService.class).searchDueElements(paymentVoucher);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  // Filling lines to pay (2nd O2M)
  public void loadSelectedLines(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucherContext = request.getContext().asType(PaymentVoucher.class);
    PaymentVoucher paymentVoucher =
        Beans.get(PaymentVoucherRepository.class).find(paymentVoucherContext.getId());

    try {
      Beans.get(PaymentVoucherLoadService.class)
          .loadSelectedLines(paymentVoucher, paymentVoucherContext);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  public void setPaidAmount(ActionRequest request, ActionResponse response) {
    PaymentVoucher paymentVoucherContext = request.getContext().asType(PaymentVoucher.class);

    Map<String, BigDecimal> map = new HashMap<>();
    BigDecimal total = BigDecimal.ZERO;
    BigDecimal remaining = BigDecimal.ZERO;
    if (paymentVoucherContext.getOperationTypeSelect()
            == PaymentVoucherRepository.OPERATION_TYPE_CLIENT_SALE_CLIENT_REFUND
        || paymentVoucherContext.getOperationTypeSelect()
            == PaymentVoucherRepository.OPERATION_TYPE_SUPPLIER_PURCHASE_SUPPLIER_REFUND) {
      map =
          Beans.get(PaymentVoucherConfirmService.class).processSaleAndRefund(paymentVoucherContext);
      for (PayVoucherDueElement each : paymentVoucherContext.getPayVoucherDueElementList()) {
        if (each.isSelected()) {
          total =
              total.add(
                  each.getMoveLine()
                      .getDebit()
                      .subtract(each.getMoveLine().getCredit())
                      .subtract(each.getPaidAmount()));
        }
      }
      remaining = paymentVoucherContext.getPaidAmount().subtract(total).abs();
    } else {
      for (PayVoucherDueElement each : paymentVoucherContext.getPayVoucherDueElementList()) {
        if (each.isSelected()) {
          total = total.add(each.getAmountRemaining());
        }
      }
    }
    if (paymentVoucherContext.getPaidAmount().compareTo(BigDecimal.ZERO) == 0
        || paymentVoucherContext.getPaidAmount() == null) {
      response.setValue("paidAmount", total.abs());
    }
    response.setValue("remainingAmount", remaining);
  }

  public void computeRemainingAmount(ActionRequest request, ActionResponse response) {
    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    BigDecimal remaining = BigDecimal.ZERO;
    BigDecimal totalElementToPay = BigDecimal.ZERO;

    if (paymentVoucher.getPaidAmount().signum() != 0) {
      if (paymentVoucher.getPayVoucherElementToPayList() != null
          || !paymentVoucher.getPayVoucherElementToPayList().isEmpty()) {
        for (PayVoucherElementToPay payVoucherElementToPay :
            paymentVoucher.getPayVoucherElementToPayList()) {
          if (payVoucherElementToPay != null) {
            if (paymentVoucher.getOperationTypeSelect() == 6) {
              totalElementToPay =
                  totalElementToPay.add(
                      payVoucherElementToPay
                          .getMoveLine()
                          .getDebit()
                          .subtract(payVoucherElementToPay.getMoveLine().getCredit()));
            } else {
              totalElementToPay =
                  totalElementToPay.add(payVoucherElementToPay.getAmountToPayCurrency());
            }
          }
        }
      }
      remaining = paymentVoucher.getPaidAmount().subtract(totalElementToPay);
    } else {
      remaining = totalElementToPay;
    }
    if (remaining.compareTo(BigDecimal.ZERO) < 0) {
      remaining = BigDecimal.ZERO;
    }
    response.setValue("remainingAmount", remaining);
  }

  public void confirmRejectView(ActionRequest request, ActionResponse response) {
    PaymentVoucher paymentVoucherContext = request.getContext().asType(PaymentVoucher.class);
    PaymentVoucher paymentVoucher =
        Beans.get(PaymentVoucherRepository.class).find(paymentVoucherContext.getId());

    ActionViewBuilder confirmView =
        ActionView.define("Confirm rejection")
            .model(Wizard.class.getName())
            .add("form", "action-payment-voucher-rejection-form")
            .param("popup", "true")
            .param("show-toolbar", "false")
            .param("show-confirm", "false")
            .param("popup-save", "false")
            .param("forceEdit", "true");

    confirmView.context("paymentVoucherId", paymentVoucher.getId());
    response.setView(confirmView.map());
  }

  public void rejectPaymentVoucher(ActionRequest request, ActionResponse response)
      throws AxelorException {

    int paymentVoucherId = (int) request.getContext().get("paymentVoucherId");
    String rejectionRaison = (String) request.getContext().get("rejectionRaison");
    String rejectionInstance = (String) request.getContext().get("$rejectedInstanceSelect");
    PaymentVoucher paymentVoucher =
        Beans.get(PaymentVoucherRepository.class).find((long) paymentVoucherId);

    // PaymentVoucher paymentVoucherCon text2 =
    // request.getContext().getParent().asType(PaymentVoucher.class);
    logger.debug(
        "paymentVoucherId {} rejectionRaison {} paymentVoucher {} rejectionInstance {}",
        paymentVoucherId,
        rejectionRaison,
        paymentVoucher,
        rejectionInstance);

    PaymentVoucher paymentVoucher2 =
        Beans.get(PaymentVoucherConfirmService.class)
            .rejectPaymentVoucher(paymentVoucher, rejectionRaison);

    // response.setReload(true);
    response.setView(
        ActionView.define("PaymentVoucher")
            .model(PaymentVoucher.class.getName())
            .add("form", "payment-voucher-cashing-form")
            .add("grid", "payment-voucher-grid")
            .context("_showRecord", paymentVoucher2.getId().toString())
            .domain("self.id = " + paymentVoucher2.getId())
            .map());
  }

  // Reset imputation
  public void resetImputation(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucherContext = request.getContext().asType(PaymentVoucher.class);
    PaymentVoucher paymentVoucher =
        Beans.get(PaymentVoucherRepository.class).find(paymentVoucherContext.getId());

    try {
      Beans.get(PaymentVoucherLoadService.class).resetImputation(paymentVoucher);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  public void askPaymentVoucher(ActionRequest request, ActionResponse response) {
    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);

    if (paymentVoucher.getHasAutoInput()) {
      PaymentMode paymentMode = paymentVoucher.getPaymentMode();
      Company company = paymentVoucher.getCompany();
      BankDetails companyBankDetails = paymentVoucher.getCompanyBankDetails();
      try {
        Journal journal =
            Beans.get(PaymentModeService.class)
                .getPaymentModeJournal(paymentMode, company, companyBankDetails);
        if (journal.getExcessPaymentOk()) {
          response.setAlert(I18n.get("No items have been selected. Do you want to continue?"));
        }
      } catch (Exception e) {
        TraceBackService.trace(response, e);
      }
    }
  }

  // Confirm the payment voucher
  public void confirmPaymentVoucher(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherConfirmService.class).confirmPaymentVoucher(paymentVoucher, false);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  // Confirm the payment voucher
  public void confirmPaymentVoucherFinal(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherConfirmService.class).confirmPaymentVoucher(paymentVoucher, true);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  // Confirm the payment voucher
  public void confirmOtherPayment(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherConfirmService.class).confirmOtherPayment(paymentVoucher);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  public void confirmCashPayment(ActionRequest request, ActionResponse response) {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherConfirmService.class).confirmCashPayment(paymentVoucher);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  public void printPaymentVoucher(ActionRequest request, ActionResponse response)
      throws AxelorException {

    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);

    String name = I18n.get("Payment voucher");
    if (!Strings.isNullOrEmpty(paymentVoucher.getReceiptNo())) {
      name += " " + paymentVoucher.getReceiptNo();
    }
    String reportType = IReport.PAYMENT_VOUCHER;

    System.out.println("mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm");
    System.out.println(paymentVoucher.getOperationTypeSelect());
    System.out.println(paymentVoucher.getDebitCreditSelect());
    System.out.println(paymentVoucher.getOperationTypeSelect() == 9);
    System.out.println(paymentVoucher.getDebitCreditSelect() == "1");

    if(paymentVoucher.getOperationTypeSelect() == 9){
      if(paymentVoucher.getDebitCreditSelect() == "1"){
          reportType  = "CashPaymentRequestDebit.rptdesign";
      }else if(paymentVoucher.getDebitCreditSelect() == "2"){
          reportType  = "CashPaymentRequestCredit.rptdesign";
      }
    }

    String fileLink =
        ReportFactory.createReport(reportType, name + "-${date}")
            .addParam("PaymentVoucherId", paymentVoucher.getId())
            .generate()
            .getFileLink();

    logger.debug("Printing " + name);

    response.setView(ActionView.define(name).add("html", fileLink).map());
  }

  /**
   * Called on load and in partner, company or payment mode change. Fill the bank details with a
   * default value.
   *
   * @param request
   * @param response
   * @throws AxelorException
   */
  public void fillCompanyBankDetails(ActionRequest request, ActionResponse response)
      throws AxelorException {
    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    PaymentMode paymentMode = paymentVoucher.getPaymentMode();
    Company company = paymentVoucher.getCompany();
    Partner partner = paymentVoucher.getPartner();
    if (company == null) {
      return;
    }
    if (partner != null) {
      partner = Beans.get(PartnerRepository.class).find(partner.getId());
    }
    BankDetails defaultBankDetails =
        Beans.get(BankDetailsService.class)
            .getDefaultCompanyBankDetails(company, paymentMode, partner, null);
    response.setValue("companyBankDetails", defaultBankDetails);
  }

  public void initFromInvoice(ActionRequest request, ActionResponse response) {
    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    @SuppressWarnings("unchecked")
    Invoice invoice =
        Mapper.toBean(Invoice.class, (Map<String, Object>) request.getContext().get("_invoice"));
    invoice = Beans.get(InvoiceRepository.class).find(invoice.getId());

    try {
      Beans.get(PaymentVoucherLoadService.class).initFromInvoice(paymentVoucher, invoice);
      response.setValues(paymentVoucher);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  public void confirmMultipleVoucher(ActionRequest request, ActionResponse response)
      throws AxelorException {
    List<PaymentVoucher> voucherList = new ArrayList<>();
    List<PayVoucherElementToPay> elementToPays = new ArrayList<>();

    @SuppressWarnings("unchecked")
    List<Integer> idList = (List<Integer>) request.getContext().get("_ids");

    for (Integer id : idList) {
      PaymentVoucher paymentVoucher =
          Beans.get(PaymentVoucherRepository.class).find(id.longValue());
      voucherList.add(paymentVoucher);
      elementToPays.addAll(paymentVoucher.getPayVoucherElementToPayList());
    }
    Move move =
        Beans.get(PaymentVoucherConfirmService.class)
            .confirmMultipleVoucher(voucherList, elementToPays, true);

    response.setView(
        ActionView.define(I18n.get("Move"))
            .model(Move.class.getName())
            .add("grid", "move-grid")
            .add("form", "move-form")
            .context("_showRecord", String.valueOf(move.getId()))
            .map());
  }

  public void getVoucherData(ActionRequest request, ActionResponse response)
      throws AxelorException {
    List<PaymentVoucher> voucherList = new ArrayList<>();
    List<PayVoucherElementToPay> elementToPays = new ArrayList<>();

    @SuppressWarnings("unchecked")
    List<Integer> idList = (List<Integer>) request.getContext().get("_ids");

    BigDecimal totalVoucher = BigDecimal.ZERO;
    BigDecimal totalAmount = BigDecimal.ZERO;
    BigDecimal totalImputedAmount = BigDecimal.ZERO;
    BigDecimal totalRemainingAmountAfterPayment = BigDecimal.ZERO;
    BigDecimal totalAmountRefund = BigDecimal.ZERO;
    BigDecimal totalImputedAmountRefund = BigDecimal.ZERO;
    BigDecimal totalRemainingAmountAfterPaymentRefund = BigDecimal.ZERO;

    for (Integer id : idList) {
      PaymentVoucher paymentVoucher =
          Beans.get(PaymentVoucherRepository.class).find(id.longValue());
      voucherList.add(paymentVoucher);
      elementToPays.addAll(paymentVoucher.getPayVoucherElementToPayList());
    }

    totalVoucher =
        voucherList
            .stream()
            .map(t -> t.getPaidAmount())
            .reduce(BigDecimal::add)
            .orElse(BigDecimal.ZERO);

    for (PayVoucherElementToPay elementPay : elementToPays) {

      Invoice invoice = elementPay.getMoveLine().getMove().getInvoice();

      if (invoice.getOperationSubTypeSelect()
              == InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_REFUNDS
          || invoice.getOperationSubTypeSelect()
              == InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND) {
        totalImputedAmountRefund = totalImputedAmount.add(elementPay.getAmountToPay());
        totalRemainingAmountAfterPaymentRefund =
            totalRemainingAmountAfterPayment.add(elementPay.getRemainingAmountAfterPayment());
        totalAmountRefund = totalAmount.add(elementPay.getTotalAmount());
      } else {
        totalImputedAmount = totalImputedAmount.add(elementPay.getAmountToPay());
        totalRemainingAmountAfterPayment =
            totalRemainingAmountAfterPayment.add(elementPay.getRemainingAmountAfterPayment());
        totalAmount = totalAmount.add(elementPay.getTotalAmount());
      }
    }

    response.setView(
        ActionView.define("Calculation")
            .model(Wizard.class.getName())
            .add("form", "account-payment-voucher-calculation-wizard-form")
            .param("popup", "true")
            .param("show-toolbar", "false")
            .param("show-confirm", "false")
            .param("width", "800")
            .param("popup-save", "false")
            .context("_totalVoucher", totalVoucher)
            .context("_totalImputedAmount", totalImputedAmount)
            .context("_totalRemainingAmountAfterPayment", totalRemainingAmountAfterPayment)
            .context("_totalAmount", totalAmount)
            .context("_totalImputedAmountRefund", totalImputedAmountRefund)
            .context(
                "_totalRemainingAmountAfterPaymentRefund", totalRemainingAmountAfterPaymentRefund)
            .context("_totalAmountRefund", totalAmountRefund)
            .map());
  }

  public void validateCashPaymentVoucher(
    ActionRequest request, ActionResponse response) throws AxelorException {
    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherConfirmService.class).validateCashPaymentVoucher(paymentVoucher);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }

  
  public void cancelCashPaymentVoucher(
      ActionRequest request, ActionResponse response) throws AxelorException {
    PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
    paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());

    try {
      Beans.get(PaymentVoucherConfirmService.class).cancelCashPaymentVoucher(paymentVoucher);
      response.setReload(true);
    } catch (Exception e) {
      TraceBackService.trace(response, e);
    }
  }
}