package com.avitam.bankloanapplication.service.impl;

import com.avitam.bankloanapplication.core.service.CoreService;
import com.avitam.bankloanapplication.exception.InvalidLoanApplicationException;
import com.avitam.bankloanapplication.model.dto.LoanApplicationDto;
import com.avitam.bankloanapplication.model.dto.LoanApplicationWsDto;
import com.avitam.bankloanapplication.model.dto.LoanDetailsDto;
import com.avitam.bankloanapplication.model.dto.LoanDto;
import com.avitam.bankloanapplication.model.dto.LoanTemplateDto;
import com.avitam.bankloanapplication.model.dto.LoanWsDto;
import com.avitam.bankloanapplication.model.entity.Customer;
import com.avitam.bankloanapplication.model.entity.Loan;
import com.avitam.bankloanapplication.model.entity.LoanApplication;
import com.avitam.bankloanapplication.model.entity.LoanLimit;
import com.avitam.bankloanapplication.model.entity.LoanScoreResult;
import com.avitam.bankloanapplication.model.entity.LoanTemplate;
import com.avitam.bankloanapplication.model.entity.LoanType;
import com.avitam.bankloanapplication.model.entity.User;
import com.avitam.bankloanapplication.pdfGenerator.PdfGenerator;
import com.avitam.bankloanapplication.repository.CustomerRepository;
import com.avitam.bankloanapplication.repository.LoanApplicationRepository;
import com.avitam.bankloanapplication.repository.LoanDetailsRepository;
import com.avitam.bankloanapplication.repository.LoanLimitRepository;
import com.avitam.bankloanapplication.repository.LoanRepository;
import com.avitam.bankloanapplication.repository.LoanScoreResultRepository;
import com.avitam.bankloanapplication.repository.LoanTemplateRepository;
import com.avitam.bankloanapplication.repository.LoanTypeRepository;
import com.avitam.bankloanapplication.repository.UserRepository;
import com.avitam.bankloanapplication.service.ImageConverterService;
import com.avitam.bankloanapplication.service.LoanApplicationService;
import com.avitam.bankloanapplication.service.LoanDetailsService;
import com.avitam.bankloanapplication.service.LoanService;
import com.avitam.bankloanapplication.service.NotificationService;
import jakarta.activation.DataHandler;
import jakarta.activation.DataSource;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.util.ByteArrayDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

@Service
@Slf4j
public class LoanApplicationServiceImpl implements LoanApplicationService {

    public static final String ADMIN_LOANAPPLICATION = "/loans/loanApplication";
    public static final String URL = "https://api.shreemeenakshifinance.com/images/";

    Logger LOG = LoggerFactory.getLogger(NotificationServiceImpl.class);
    @Autowired
    private ModelMapper modelMapper;
    @Autowired
    private CoreService coreService;
    @Autowired
    private LoanApplicationRepository loanApplicationRepository;
    @Autowired
    private CustomerRepository customerRepository;
    @Autowired
    private LoanRepository loanRepository;
    @Autowired
    private LoanTemplateRepository loanTemplateRepository;
    @Autowired
    private LoanDetailsRepository loanDetailsRepository;
    @Autowired
    private LoanScoreResultRepository loanScoreResultRepository;
    @Autowired
    private LoanLimitRepository loanLimitRepository;
    @Autowired
    private LoanTypeRepository loanTypeRepository;
    @Autowired
    private NotificationService notificationService;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private PdfGenerator pdfGenerator;
    @Autowired
    private ImageConverterService imageConverterService;
    @Autowired
    private LoanService loanService;
    @Autowired
    private LoanDetailsService loanDetailsService;
    @Value("${spring.mail.username}")
    private String username;
    @Value("${spring.mail.password}")
    private String password;

    @Override
    public LoanApplicationWsDto handleEdit(LoanApplicationWsDto request) {
        List<LoanApplicationDto> loanApplicationDtos = request.getLoanApplicationDtos();
        List<LoanApplication> loanApplications = new ArrayList<>();
        boolean isNewLoanCreated = false;

        for (LoanApplicationDto dto : loanApplicationDtos) {
            LoanApplication loanApp;

            if (dto.getRecordId() != null) {
                loanApp = loanApplicationRepository.findByRecordId(dto.getRecordId());
                if (loanApp == null) continue;
                //getLoanType(loanApp);
                modelMapper.map(dto, loanApp);
                request.setMessage("Data updated successfully");
            } else {
                loanApp = modelMapper.map(dto, LoanApplication.class);
                loanApp.setStatus(true);
                loanApp.setLoanStatus("Applied");
                loanApp.setCreationTime(new Date());
                loanApp.setRecordId(String.valueOf(System.currentTimeMillis()));
                getLoanTypeId(loanApp);
                isNewLoanCreated = true;
                request.setMessage("Data added Successfully");
            }
            Customer customer = customerRepository.findByRecordId(dto.getCustomerId());
            if (customer != null) {
                if (StringUtils.isNotEmpty(dto.getImages())) {
                    loanApp.setImages(URL + imageConverterService.saveImageToPath(dto.getImages(), "selfie_" + customer.getRecordId()));
                }
                loanApplicationRepository.save(loanApp);
                try {
                    sendEmail(customer.getEmail(), loanApp.getRecordId());
                } catch (IOException | MessagingException e) {
                    LOG.error(e.getMessage(), e);
                }
            } else {
                loanApplicationRepository.save(loanApp);
            }

            //getCustomer(loanApp);
            //getLoanTemplate(loanApp);
            loanApplications.add(loanApp);
        }

        request.setBaseUrl(ADMIN_LOANAPPLICATION);
        Type listType = new TypeToken<List<LoanApplicationDto>>() {
        }.getType();
        request.setLoanApplicationDtos(modelMapper.map(loanApplications, listType));

        if (isNewLoanCreated) {
            User user = userRepository.findByEmail("admin@gmail.com");
            if (user != null && user.getNotificationId() != null) {
                notificationService.sendNotificationAdmin(
                        "New Loan Applied",
                        "New loan has been applied, please review",
                        user.getNotificationId()
                );
            } else {
                log.warn("Admin user not found or missing notificationId");
            }
        }
        return request;
    }

    private void getLoanTypeId(LoanApplication loanApp) {
        LoanTemplate loanTemplate = loanTemplateRepository.findByRecordId(loanApp.getLoanTemplateId());
        LoanType loanType = loanTypeRepository.findByRecordId(loanTemplate.getLoanType());
        loanApp.setLoanTypeId(loanType.getRecordId());
    }

    @Override
    public void updateLoanStatus(LoanApplicationDto loanApplicationDto) {
        LoanApplication loanApplication = loanApplicationRepository.findByRecordId(loanApplicationDto.getRecordId());

        loanApplication.setLoanStatus(loanApplicationDto.getLoanStatus());
        loanApplicationRepository.save(loanApplication);

        Customer customer = customerRepository.findByRecordId(loanApplication.getCustomerId());

        if (loanApplication.getLoanStatus().equalsIgnoreCase("Approved")) {
            LoanTemplateDto loanTemplateDto = modelMapper.map(
                    loanTemplateRepository.findByRecordId(loanApplication.getLoanTemplateId()),
                    LoanTemplateDto.class
            );
            loanTemplateDto.setRecordId(null);
            LoanDto loanDto = modelMapper.map(loanTemplateDto, LoanDto.class);
            loanDto.setId(null);
            loanDto.setRecordId(null);
            loanDto.setCustomerId(loanApplication.getCustomerId());

            LoanWsDto loanWsDto1 = loanService.createLoan(loanDto);

            Type listType = new TypeToken<List<Loan>>() {
            }.getType();
            List<Loan> loanList = modelMapper.map(loanWsDto1.getLoanDtoList(), listType);
            for (Loan loanDto1 : loanList) {
                Loan loan = loanRepository.findByRecordId(loanDto1.getRecordId());
                loanApplication.setLoanId(loan.getRecordId());
                loanApplicationRepository.save(loanApplication);
                LoanDetailsDto loanDetailsDto = new LoanDetailsDto();
                loanDetailsDto.setLoanId(loan.getRecordId());
                loanDetailsService.createLoanDetailsForLoan(loanDetailsDto, loanApplication);
            }
        } else if (loanApplication.getLoanStatus().equalsIgnoreCase("Rejected")) {
            if (customer != null && customer.getNotificationId() != null) {
                notificationService.sendNotificationById(
                        "Loan Rejected",
                        "Sorry!!, your loan has been rejected",
                        customer.getNotificationId()
                );
            }
        }
    }

    /*public void getLoanType(LoanApplication loanApplication) {
        LoanTemplate loanTemplate = loanTemplateRepository.findByRecordId(loanApplication.getLoanTemplateId());
        LoanType loanType = loanTypeRepository.findByRecordId(loanTemplate.getLoanType());
        Type listType = new TypeToken<LoanTypeDto>() {
        }.getType();
        LoanTypeDto loanTypeDto = modelMapper.map(loanType, listType);
        loanApplication.setLoanTypeDto(loanTypeDto);
    }*/

    /*public void getLoanTemplate(LoanApplication loanApplication) {
        LoanTemplate loanTemplate = loanTemplateRepository.findByRecordId(loanApplication.getLoanTemplateId());
        Type listType = new TypeToken<LoanTemplateDto>() {
        }.getType();
        LoanTemplateDto loanTemplateDto = modelMapper.map(loanTemplate, listType);
        loanApplication.setLoanTemplateDto(loanTemplateDto);
    }*/

    /*public void getCustomer(LoanApplication loanApplication) {
        Customer customer = customerRepository.findByRecordId(loanApplication.getCustomerId());
        Type listType = new TypeToken<CustomerDto>() {
        }.getType();
        CustomerDto customerDto = modelMapper.map(customer, listType);
        loanApplication.setCustomerDto(customerDto);
    }*/

    public LoanWsDto getLoanApplicationResult(LoanApplicationWsDto request) {
        LoanWsDto loanWsDto = new LoanWsDto();
        List<LoanApplicationDto> loanAppDto = request.getLoanApplicationDtos();
        LoanApplication finalizedApplication = new LoanApplication();
        for (LoanApplicationDto applicationDto : loanAppDto) {

            finalizedApplication = finalizeLoanApplication(applicationDto);
        }
        if (finalizedApplication == null) {
            throw new InvalidLoanApplicationException("No loan application finalized.");
        }

        Loan loan = loanRepository.findByRecordId(finalizedApplication.getLoanId());
        LoanScoreResult loanScoreResult = loanScoreResultRepository.findByRecordId(loan.getLoanScoreResultId());
        List<LoanDto> loanDtoList = new ArrayList<>();
        if (loanScoreResult.getName().equalsIgnoreCase("NOT_RESULTED")) {
            throw new InvalidLoanApplicationException("!");
        } else if (loanScoreResult.getName().equalsIgnoreCase("REJECTED")) {
            LoanDto loanDto = new LoanDto();
            modelMapper.map(loan, loanDto);
            loanDtoList.add(loanDto);
            loanWsDto.setLoanDtoList(loanDtoList);
            return loanWsDto;
        }
        LoanDto loanDto = new LoanDto();
        modelMapper.map(loan, loanDto);
        loanDtoList.add(loanDto);
        loanWsDto.setLoanDtoList(loanDtoList);

        return loanWsDto;
    }

    private LoanApplication finalizeLoanApplication(LoanApplicationDto applicationDto) {

        LoanApplication loanApplication = loanApplicationRepository.findByRecordId(applicationDto.getRecordId());
        if (loanApplication != null) {
            verifyLoan(loanApplication);
        }

        return loanApplication;

    }

    private void verifyLoan(LoanApplication loanApplication) {
        String loanCustomer = loanApplication.getCustomerId();

        Loan loanToUpdate = getNotResultedLoanApplicationOfCustomer(loanApplication);
        if (loanToUpdate == null) return;
        log.info("Getting loan application for result");

        Customer customer = customerRepository.findByRecordId(loanCustomer);

        Integer loanScore = customer.getLoanScore();
        LoanScoreResult loanScoreResult = loanScoreResultRepository.findByName("REJECTED");
        boolean loanScoreForApproval = (loanScore >= loanScoreResult.getLoanScoreLimit());

        if (loanScoreForApproval) {
            LoanScoreResult loanScoreResult2 = loanScoreResultRepository.findByName("APPROVED");
            loanToUpdate.setLoanScoreResultId(loanScoreResult2.getRecordId());
            loanApplication.setLoanId(loanToUpdate.getRecordId());
            loanRepository.save(loanToUpdate);
            loanToUpdate = loanLimitCalculator(loanApplication);

        } else {
            loanToUpdate.setLoanScoreResultId(loanScoreResult.getRecordId());
            loanRepository.save(loanToUpdate);
        }
        loanRepository.save(loanToUpdate);
        log.info("resulted the application");
        //TODO: modify sms
        log.info("Sent sms result");
    }

    private Loan loanLimitCalculator(LoanApplication loanApplication) {

        Loan loan = loanRepository.findByRecordId(loanApplication.getLoanId());
        Loan updatedLoan = modelMapper.map(loan, Loan.class);
        Customer customer = customerRepository.findByRecordId(loanApplication.getCustomerId());

        BigDecimal loanScore = BigDecimal.valueOf(customer.getLoanScore());
        BigDecimal income = BigDecimal.valueOf(customer.getMonthlyIncome());
        BigDecimal loanMultiplier = loan.getCreditMultiplier();

        LoanLimit loanLimit1 = loanLimitRepository.findByName("HIGHER");
        LoanLimit loanLimit2 = loanLimitRepository.findByName("SPECIAL");
        boolean loanLimitCheck = income.compareTo(loanLimit1.getLoanLimitAmount()) >= 0;
        LoanScoreResult loanScoreResult = loanScoreResultRepository.findByName("APPROVED");
        boolean loanScoreCheck = loanScore.compareTo(BigDecimal.valueOf(loanScoreResult.getLoanScoreLimit())) >= 0;
        LoanLimit loanLimit3 = loanLimitRepository.findByName("LOWER");

        if (loanScoreCheck) {
            loanLimit2.setLoanLimitAmount(income.multiply(loanMultiplier));
            //   loan.setLoanLimit(loanLimit2.getLoanLimitAmount());
        } else if (loanLimitCheck) {
            //   loan.setLoanLimit(loanLimit1.getLoanLimitAmount());
        } else {
            //  loan.setLoanLimit(loanLimit3.getLoanLimitAmount());
        }
        return loan;
    }

    private Loan getNotResultedLoanApplicationOfCustomer(LoanApplication loanApplication) {
        String loanId = loanApplication.getLoanId();
        Loan loan = loanRepository.findByRecordId(loanId);
        LoanScoreResult loanScoreResult = loanScoreResultRepository.findByRecordId(loan.getLoanScoreResultId());
        return loan;
    }

    public void sendApprovedEmailToCustomer(String loanApplicationId) {
        pdfGenerator.sanctionLetterPdf(loanApplicationId);
    }

    public Object getLoanApplicationById(Long id) {
        return null;
    }

    private void sendEmail(String recipientEmail, String loanApplicationId) throws IOException, MessagingException {

        Properties properties = new Properties();
        properties.put("mail.transport.protocol", "smtp");  // Protocol (SMTP)
        properties.put("mail.smtp.auth", "true");           // Enable authentication
        properties.put("mail.smtp.starttls.enable", "true"); // Use TLS
        properties.put("mail.smtp.host", "smtp.gmail.com");  // SMTP Host
        properties.put("mail.smtp.port", "587");             // SMTP Port
        properties.put("mail.smtp.ssl.trust", "*");
        properties.put("mail.debug", "true");

        Session session = Session.getInstance(properties, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(username, password);  // Use your email and app password
            }
        });

        MimeMessage message = new MimeMessage(session);
        message.setFrom(new InternetAddress(username));
        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail));

        Multipart multipart = new MimeMultipart();
        MimeBodyPart textPart = new MimeBodyPart();
        textPart.setText("Please find the attached PDF document.");
        multipart.addBodyPart(textPart);

        ByteArrayOutputStream byteArrayOutputStream = pdfGenerator.sanctionLetterPdf(loanApplicationId);

        byte[] responseBytes = byteArrayOutputStream.toByteArray();

        Files.write(Paths.get("Sanction_Letter.pdf"), responseBytes);

        MimeBodyPart pdfPart = new MimeBodyPart();
        DataSource dataSource = new ByteArrayDataSource(responseBytes, "application/pdf");
        pdfPart.setDataHandler(new DataHandler(dataSource));
        pdfPart.setFileName("Sanction_Letter.pdf");

        multipart.addBodyPart(pdfPart);

        message.setContent(multipart);
        Transport.send(message);
    }
}