package com.avitam.bankloanapplication.service.impl;

import com.avitam.bankloanapplication.model.dto.LoanEmiDetailDto;
import com.avitam.bankloanapplication.model.dto.PaymentDetailsDto;
import com.avitam.bankloanapplication.model.dto.PaymentHistoryDto;
import com.avitam.bankloanapplication.model.dto.PaymentHistoryWsDto;
import com.avitam.bankloanapplication.model.entity.Loan;
import com.avitam.bankloanapplication.model.entity.PaymentHistory;
import com.avitam.bankloanapplication.repository.LoanRepository;
import com.avitam.bankloanapplication.repository.PaymentHistoryRepository;
import com.avitam.bankloanapplication.service.PaymentHistoryService;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
public class PaymentHistoryServiceImpl implements PaymentHistoryService {

    public static final String ADMIN_PAYMENTHISTORY = "/loans/paymentHistory";

    @Autowired
    private PaymentHistoryRepository paymentHistoryRepository;
    @Autowired
    private ModelMapper modelMapper;
    @Autowired
    private LoanRepository loanRepository;

    @Override
    public PaymentHistoryWsDto createPaymentHistory(PaymentHistoryWsDto request) {
        PaymentHistory paymentHistory = new PaymentHistory();
        List<PaymentHistoryDto> paymentHistoryDtoList = request.getPaymentHistoryDtoList();

        List<PaymentHistory> paymentHistoryList = new ArrayList<>();
        for (PaymentHistoryDto paymentHistoryDto : paymentHistoryDtoList) {
            boolean exists = paymentHistoryRepository.existsByLoanId(paymentHistoryDto.getLoanDto().getRecordId());

            // Ensure loan is fetched first and EMI adjustments / foreclosure applied
            getLoanByLoanId(paymentHistoryDto);               // sets paymentDetailsDtoList with loanEmiDetail mapped
            adjustEmiPaidData(paymentHistoryDto);             // adjust pending amounts based on paid amount
            updateEmiWithPayment(paymentHistoryDto);          // apply foreclosure or normal EMI updates to loan and save

            if (exists) {
                // merge with existing payment history
                paymentHistory = paymentHistoryRepository.findByLoanId(paymentHistoryDto.getLoanDto().getRecordId());

                List<PaymentDetailsDto> paymentDetailsDtoList = new ArrayList<>();
                if (paymentHistory.getPaymentDetailsDtoList() != null) {
                    paymentDetailsDtoList.addAll(paymentHistory.getPaymentDetailsDtoList());
                }
                if (paymentHistoryDto.getPaymentDetailsDtoList() != null) {
                    paymentDetailsDtoList.addAll(paymentHistoryDto.getPaymentDetailsDtoList());
                }
                paymentHistoryDto.setPaymentDetailsDtoList(paymentDetailsDtoList);

                modelMapper.map(paymentHistoryDto, paymentHistory);
                paymentHistoryRepository.save(paymentHistory);
                request.setMessage("Data updated successfully");
            } else {
                // new payment history
                paymentHistory = modelMapper.map(paymentHistoryDto, PaymentHistory.class);
                paymentHistory.setStatus(true);
                paymentHistory.setCreationTime(new Date());
                paymentHistory.setLoanId(paymentHistoryDto.getLoanDto().getRecordId());
                getCustomerId(paymentHistoryDto);
                paymentHistoryRepository.save(paymentHistory);
            }

            if (request.getRecordId() == null) {
                paymentHistory.setRecordId(String.valueOf(paymentHistory.getId().getTimestamp()));
            }

            paymentHistoryRepository.save(paymentHistory);

            paymentHistoryList.add(paymentHistory);
            paymentHistoryDto.setBaseUrl(ADMIN_PAYMENTHISTORY);

            request.setMessage("Data added Successfully");
        }
        Type listType = new TypeToken<List<PaymentHistoryDto>>() {
        }.getType();
        request.setPaymentHistoryDtoList(modelMapper.map(paymentHistoryList, listType));

        return request;
    }

    /**
     * Adjusts EMI pending amounts based on the incoming payment DTO.
     */
    private void adjustEmiPaidData(PaymentHistoryDto paymentHistoryDto) {
        Loan loan = loanRepository.findByRecordId(paymentHistoryDto.getLoanDto().getRecordId());
        if (loan == null || loan.getLoanEmiDetailDtoList() == null || loan.getLoanEmiDetailDtoList().isEmpty()) {
            return;
        }

        PaymentDetailsDto paymentDetailsDto = paymentHistoryDto.getPaymentDetailsDtoList().get(0);
        BigDecimal currentPaidEmi = paymentDetailsDto.getEmiPaid() != null ? paymentDetailsDto.getEmiPaid() : BigDecimal.ZERO;

        Optional<LoanEmiDetailDto> hasPendingEntry = loan.getLoanEmiDetailDtoList()
                .stream()
                .filter(loanEmiDetailDto -> loanEmiDetailDto.getPendingEmiAmount() != null
                        && loanEmiDetailDto.getPendingEmiAmount().compareTo(BigDecimal.ZERO) > 0)
                .findAny();

        List<LoanEmiDetailDto> loanEmiDetailDtoList = new ArrayList<>();
        for (LoanEmiDetailDto loanEmiDetailDto : loan.getLoanEmiDetailDtoList()) {
            BigDecimal totalPayable = loanEmiDetailDto.getTotalPayable() != null ? loanEmiDetailDto.getTotalPayable() : BigDecimal.ZERO;
            BigDecimal pending = loanEmiDetailDto.getPendingEmiAmount() != null ? loanEmiDetailDto.getPendingEmiAmount() : BigDecimal.ZERO;

            if (pending.compareTo(BigDecimal.ZERO) > 0) {
                BigDecimal pendingAmt = totalPayable.add(pending).subtract(currentPaidEmi);
                if (pendingAmt.compareTo(BigDecimal.valueOf(100)) > 0) {
                    loanEmiDetailDto.setPendingEmiAmount(pendingAmt);
                } else {
                    loanEmiDetailDto.setPendingEmiAmount(BigDecimal.ZERO);
                }
            } else {
                if (hasPendingEntry.isEmpty()
                        && paymentDetailsDto.getMonthlyIndex() == Integer.parseInt(loanEmiDetailDto.getRecordId())
                        && totalPayable.compareTo(currentPaidEmi) != 0) {
                    BigDecimal newPending = totalPayable.subtract(currentPaidEmi);
                    if (newPending.compareTo(BigDecimal.ZERO) > 0) {
                        loanEmiDetailDto.setPendingEmiAmount(newPending);
                    } else {
                        loanEmiDetailDto.setPendingEmiAmount(BigDecimal.ZERO);
                    }
                }
            }
            loanEmiDetailDtoList.add(loanEmiDetailDto);
        }
        loan.setLoanEmiDetailDtoList(loanEmiDetailDtoList);
        loanRepository.save(loan);
    }

    public void getLoanByLoanId(PaymentHistoryDto paymentHistoryDto) {
        Loan loan = loanRepository.findByRecordId(paymentHistoryDto.getLoanDto().getRecordId());
        List<PaymentDetailsDto> paymentDetailsDtoList = new ArrayList<>();
        if (loan == null || loan.getLoanEmiDetailDtoList() == null) {
            paymentHistoryDto.setPaymentDetailsDtoList(paymentDetailsDtoList);
            return;
        }

        for (PaymentDetailsDto paymentDetailsDto : paymentHistoryDto.getPaymentDetailsDtoList()) {
            for (LoanEmiDetailDto loanEmiDetailDto : loan.getLoanEmiDetailDtoList()) {
                int month = Integer.parseInt(loanEmiDetailDto.getRecordId());
                if (paymentDetailsDto.getMonthlyIndex() == month) {
                    PaymentDetailsDto paymentDetailsDto1 = new PaymentDetailsDto();
                    loanEmiDetailDto.setPaymentStatus("Paid");

                    paymentDetailsDto1.setLoanEmiDetailDto(loanEmiDetailDto);
                    paymentDetailsDto1.setPaidStatus(paymentDetailsDto.getPaidStatus());
                    paymentDetailsDto1.setMonthlyIndex(paymentDetailsDto.getMonthlyIndex());
                    paymentDetailsDto1.setTransactionId(paymentDetailsDto.getTransactionId());
                    paymentDetailsDto1.setEmiPaid(paymentDetailsDto.getEmiPaid());
                    paymentDetailsDtoList.add(paymentDetailsDto1);
                    break;
                }
            }
        }
        paymentHistoryDto.setPaymentDetailsDtoList(paymentDetailsDtoList);
    }

    private void getCustomerId(PaymentHistoryDto paymentHistoryDto) {
        Loan loan = loanRepository.findByRecordId(paymentHistoryDto.getLoanDto().getRecordId());
        if (loan != null) {
            paymentHistoryDto.setCustomerId(loan.getCustomerId());
        }
    }

    private void updateEmiWithPayment(PaymentHistoryDto paymentHistoryDto) {
        Loan loan = loanRepository.findByRecordId(paymentHistoryDto.getLoanDto().getRecordId());
        if (loan == null || loan.getLoanEmiDetailDtoList() == null) return;

        BigDecimal foreclosureAmount = calculateForeclosureAmount(loan);

        for (PaymentDetailsDto payment : paymentHistoryDto.getPaymentDetailsDtoList()) {
            BigDecimal emiPaid = payment.getEmiPaid() != null ? payment.getEmiPaid() : BigDecimal.ZERO;
            int monthIndex = payment.getMonthlyIndex();

            LoanEmiDetailDto currentEmi = null;
            for (LoanEmiDetailDto e : loan.getLoanEmiDetailDtoList()) {
                if (Integer.parseInt(e.getRecordId()) == monthIndex) {
                    currentEmi = e;
                    break;
                }
            }

            if (currentEmi == null) continue;

            if (emiPaid.compareTo(foreclosureAmount) >= 0) {
                // Foreclosure triggered
                currentEmi.setTotalPayable(foreclosureAmount);
                currentEmi.setPaymentStatus("Paid");

                // Set due date to today
                currentEmi.setDueDate(java.time.LocalDate.now());

                for (LoanEmiDetailDto futureEmi : loan.getLoanEmiDetailDtoList()) {
                    int futureIndex = Integer.parseInt(futureEmi.getRecordId());
                    if (futureIndex > monthIndex) {
                        futureEmi.setTotalPayable(BigDecimal.ZERO);
                        futureEmi.setPaymentStatus("Paid");
                        futureEmi.setPendingEmiAmount(BigDecimal.ZERO);
                    }
                }

                loan.setLoanStatus("Closed");
                loan.setForeClosing(true);
                loan.setPendingInstallmentAmount(BigDecimal.ZERO);
                loan.setPendingTotalEmiAmount(BigDecimal.ZERO);
            }


        else {
                BigDecimal totalPayable = currentEmi.getTotalPayable() != null ? currentEmi.getTotalPayable() : BigDecimal.ZERO;
                BigDecimal pending = totalPayable.subtract(emiPaid);
                currentEmi.setPendingEmiAmount(pending.compareTo(BigDecimal.ZERO) <= 0 ? BigDecimal.ZERO : pending);
                currentEmi.setPaymentStatus("Paid");
            }
        }

        loanRepository.save(loan);
    }

    private BigDecimal calculateForeclosureAmount(Loan loan) {
        BigDecimal amount = BigDecimal.ZERO;
        if (loan.getLoanEmiDetailDtoList() != null) {
            for (LoanEmiDetailDto emi : loan.getLoanEmiDetailDtoList()) {
                if (emi.getPaymentStatus() == null || !"Paid".equalsIgnoreCase(emi.getPaymentStatus())) {
                    BigDecimal instalment = emi.getInstalment() != null ? emi.getInstalment() : BigDecimal.ZERO;
                    BigDecimal interest = emi.getInterestAmount() != null ? emi.getInterestAmount() : BigDecimal.ZERO;
                    BigDecimal penalty = emi.getPenalty() != null ? emi.getPenalty() : BigDecimal.ZERO;
                    amount = amount.add(instalment).add(interest).add(penalty);
                }
            }
        }

        if (loan.getForeClosingCharges() != null) {
            amount = amount.add(loan.getForeClosingCharges());
        }

        return amount;
    }

}
