package eu.dnetlib.goldoa.service;

import eu.dnetlib.goldoa.domain.*;
import eu.dnetlib.goldoa.service.dao.RequestDAO;
import eu.dnetlib.goldoa.service.utils.EmailUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;

import javax.mail.MessagingException;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * Created by antleb on 3/10/15.
 */
@Transactional
public class RequestManagerImpl implements RequestManager {

    @Autowired
    private RequestDAO requestDAO;
    @Autowired
    private PersonManager personManager;
    @Autowired
    private OrganizationManager organizationManager;
    @Autowired
    private ProjectManager projectManager;
    @Autowired
    private PublicationManager publicationManager;
    @Autowired
    private InvoiceManager invoiceManager;
    @Autowired
    private BudgetManager budgetManager;
    @Autowired
    private EligibilityManager eligibilityManager;
    @Autowired
    private EmailUtils emailUtils;
    @Autowired
    private ExecutorService executorService;

    private boolean sendCoordinatorEmails = false;

	@Override
	public Request saveRequest(final Request request) {

		if (request.getId() == null) {
			request.setId(new SimpleDateFormat("yyyyMMdd-").format(new Date()) + requestDAO.getRequestId());
		}

        if (request.getInvoice() != null)
            request.addStatus(Request.RequestStatus.INVOICE_UPLOADED);

        requestDAO.saveRequest(request);
        requestDAO.saveCoFunders(request);

		return request;
	}

	@Override
	public RequestInfo getById(String requestId) {
        try {
            Request request = requestDAO.getRequest(requestId);

            request.setCoFunders(requestDAO.getCoFunders(request));

            return this.getRequestInfo(request);
        } catch (PersonManagerException e) {
            e.printStackTrace();
        }

        return null;
	}

    @Override
    public List<RequestInfo> getForUser(String personId, Date fromDate, Date toDate, RequestSort requestSortBy, RequestSortOrder order, RequestFilter requestFilter, String term, Request.RequestStatus[] statusFilter, int from, int to) {
        List<Request> requests = requestDAO.getForUser(personId, fromDate, toDate, requestSortBy, order, requestFilter, term, statusFilter, from, to);
        List<RequestInfo> res = new ArrayList<RequestInfo>();

        for (Request request:requests) {
            try {
                request.setCoFunders(requestDAO.getCoFunders(request));

                RequestInfo req = getShortRequestInfo(request);

                res.add(req);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return res;
    }

    @Override
    public List<RequestInfo> getForPublisher(String publisherId, Date fromDate, Date toDate, RequestSort requestSortBy, RequestSortOrder order, RequestFilter requestFilter, String term, Request.RequestStatus[] statusFilter, int from, int to) {
        List<Request> requests = requestDAO.getForPublisher(publisherId, fromDate, toDate, requestSortBy, order, requestFilter, term, statusFilter, from, to);
        List<RequestInfo> res = new ArrayList<RequestInfo>();

        for (Request request:requests) {
            try {
                request.setCoFunders(requestDAO.getCoFunders(request));

                RequestInfo req = getShortRequestInfo(request);

                res.add(req);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return res;
    }

    @Override
    public List<RequestInfo> getForOrganization(List<String> organizationIds, Date fromDate, Date toDate, RequestSort requestSortBy, RequestSortOrder order, RequestFilter requestFilter, String term, Request.RequestStatus[] statusFilter, int from, int to) {
        List<Request> requests = requestDAO.getForOrganization(organizationIds, fromDate, toDate, requestSortBy, order, requestFilter, term, statusFilter, from, to);
        List<RequestInfo> res = new ArrayList<RequestInfo>();

        for (Request request:requests) {
            try {
                request.setCoFunders(requestDAO.getCoFunders(request));

                RequestInfo req = getShortRequestInfo(request);

                res.add(req);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return res;
    }

    @Override
	public List<Request> getForProject(String projectId) {
		return requestDAO.getForProject(projectId);
	}

    @Override
    public List<RequestInfo> getRequests(Date fromDate, Date toDate, RequestSort requestSortBy, RequestSortOrder order, RequestFilter requestFilter, String term, Request.RequestStatus[] statusFilter, int from, int to) {
        List<Request> requests = requestDAO.getRequests(fromDate, toDate, requestSortBy, order, requestFilter, term, statusFilter, from, to);
        List<RequestInfo> res = new ArrayList<RequestInfo>();

        for (Request request:requests) {
            try {
                request.setCoFunders(requestDAO.getCoFunders(request));

                RequestInfo req = getShortRequestInfo(request);

                res.add(req);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return res;
    }

    @Override
    public Request submitRequest(final Request request) {

        request.addStatus(Request.RequestStatus.SUBMITTED);

        Budget budget = null; //getBudgetForRequest(request);

        if  (budget != null) {
            request.setBudget(budget.getId());

            budget.setRemaining(budget.getRemaining() - request.getFundingRequested());
            budgetManager.saveBudget(budget);
        }

        saveRequest(request);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = null;
                try {
                    requestInfo = getRequestInfo(request);

                    switch (requestInfo.getEligibility().getStatus()) {
                        case OK:
                            emailUtils.sendRequesterOKEmail(requestInfo);

                            for (Person person : personManager.getModerators())
                                emailUtils.sendModeratorOKEmail(requestInfo, person);
                            break;
                        case IFFY:
                            emailUtils.sendRequesterIFFYEmail(requestInfo);

                            for (Person person : personManager.getModerators())
                                emailUtils.sendModeratorIFFYEmail(requestInfo, person);
                            break;
                        case NONO:
                            break;
                    }

                    if (sendCoordinatorEmails) {
                        for (Person coordinator:requestInfo.getProject().getCoordinators())
                            emailUtils.sendCoordinatorRequestSubmittedEmail(coordinator, requestInfo);
                    }
                } catch (MessagingException e) {
                    e.printStackTrace();
                } catch (PersonManagerException e) {
                    e.printStackTrace();
                } catch (Exception e) {
					e.printStackTrace();
				}
            }
        });

        return request;
    }

    @Override
    public void approveRequest(final String requestId, String comment) {
        requestDAO.approveRequest(requestId, comment);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = getById(requestId);

                try {
                    emailUtils.sendRequesterApprovedEmail(requestInfo);
                } catch (MessagingException e) {
                    e.printStackTrace();
                }

                if (requestInfo.getInvoice() == null) {
                    try {
                        emailUtils.sendRequesterInvoiceNotification(requestInfo);
                        emailUtils.sendPublisherInvoiceNotification(requestInfo);
                    } catch (MessagingException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        for (Person person:personManager.getAccountingOfficers())
                            emailUtils.sendAccountingRequestApprovedEmail(person, requestInfo);
                    } catch (MessagingException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Override
    public void rejectRequest(final String requestId, String comment) {
        requestDAO.rejectRequest(requestId, comment);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = getById(requestId);

                try {
                    emailUtils.sendRequesterRejectedEmail(requestInfo);
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void paidRequest(final String requestId, String comment, float apc_paid, float transfer_cost, float other_cost, Date datePaid) {
        requestDAO.paidRequest(requestId, comment, apc_paid, transfer_cost, other_cost, datePaid);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = getById(requestId);

                try {
                    emailUtils.sendRequesterPaidEmail(requestInfo);
                    emailUtils.sendPublisherPaidEmail(requestInfo);
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void onHoldRequest(final String requestId, String comment) {
        requestDAO.onHoldRequest(requestId, comment);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = getById(requestId);

                try {
                    emailUtils.sendRequesterOnHoldEmail(requestInfo);
                    emailUtils.sendPublisherOnHoldEmail(requestInfo);
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void deniedRequest(final String requestId, String comment) {
        requestDAO.deniedRequest(requestId, comment);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = getById(requestId);

                try {
                    emailUtils.sendRequesterDeniedEmail(requestInfo);
                    emailUtils.sendPublisherDeniedEmail(requestInfo);
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void invoiceUploaded(final String requestId, String invoiceId) {
        requestDAO.invoiceUploaded(requestId, invoiceId);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                RequestInfo requestInfo = getById(requestId);

                if (requestInfo.getRequest().getStatus(Request.RequestStatus.APPROVED)) {
                    try {
                        for (Person person : personManager.getAccountingOfficers())
                            emailUtils.sendAccountingRequestApprovedEmail(person, requestInfo);
                    } catch (MessagingException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Override
    public void uploadTransferReceipt(String requestid, String contentType, InputStream inputStream) {
        requestDAO.uploadBankTransferReceipt(requestid, contentType, inputStream);
    }

    @Override
    public BankTransferReceipt downloadBankTransferReceipt(String requestId) throws RequestManagerException {
        try {
            return requestDAO.downloadBankTransferReceipt(requestId);
        } catch (EmptyResultDataAccessException e) {
            throw new RequestManagerException(RequestManagerException.ErrorCause.NOT_EXISTS);
        }
    }

    private Request.RequestStatus getStatus(Request request) {
        if (request.getStatus(Request.RequestStatus.SUBMITTED)) {
            if (request.getStatus(Request.RequestStatus.APPROVED)) {
                if (request.getStatus(Request.RequestStatus.ACCOUNTING_PAID))
                    return Request.RequestStatus.ACCOUNTING_PAID;
                else if (request.getStatus(Request.RequestStatus.ACCOUNTING_ON_HOLD))
                    return Request.RequestStatus.ACCOUNTING_ON_HOLD;
                else if (request.getStatus(Request.RequestStatus.ACCOUNTING_DENIED))
                    return Request.RequestStatus.ACCOUNTING_DENIED;
                else
                    return Request.RequestStatus.APPROVED;
            }
            else if (request.getStatus(Request.RequestStatus.REJECTED))
                return Request.RequestStatus.REJECTED;
            else
                return Request.RequestStatus.SUBMITTED;
        }
        else
            return Request.RequestStatus.INCOMPLETE;
    }



    private RequestInfo getRequestInfo(Request request) throws PersonManagerException {
        RequestInfo req = new RequestInfo();

        try {
            // TODO add missing fields
            req.setId(request.getId());
            req.setUser(personManager.getById(request.getUser()));
            req.setDate(request.getDate());
            req.setResearcher(personManager.getById(request.getResearcher()));
            req.setOrganization(organizationManager.getById(request.getOrganization()));
            req.setProject(projectManager.getById(request.getProject()).getProject());
            req.setPublication(publicationManager.getPublication(request.getPublication()));
            req.setPublisherEmail(request.getPublisherEmail());
            req.setApc(request.getApc());
            req.setDiscount(request.getDiscount());
            req.setProjectParticipation(request.getProjectParticipation());
            req.setFundingRequested(request.getFundingRequested());
            req.setCurrency(request.getCurrency());
            req.setBankAccount(request.getBankAccount());
            if(request.getInvoice()!=null)
                req.setInvoice(invoiceManager.getInvoice(request.getInvoice()));
            req.setEligibility(eligibilityManager.validate(request));
            req.setStatus(getStatus(request));
            req.setComments(requestDAO.getComments(request.getId()));
            req.setCoFunders(request.getCoFunders());
            req.setRequest(request);

            req.setApc_paid(request.getApc_paid());
            req.setTransfer_cost(request.getTransfer_cost());
            req.setOther_cost(request.getOther_cost());
            req.setDate_paid(request.getDate_paid());
        } catch (ManagerException me) {
            me.printStackTrace();
        } catch (OrganizationManagerException e) {
            e.printStackTrace();
        }

        return req;
    }

    private RequestInfo getShortRequestInfo(Request request) throws PersonManagerException {
        RequestInfo req = new RequestInfo();

        try {
        // TODO add missing fields
            req.setId(request.getId());
            req.setDate(request.getDate());
            req.setProject(projectManager.getById(request.getProject()).getProject());
            req.setPublication(publicationManager.getPublication(request.getPublication()));
            req.setPublisherEmail(request.getPublisherEmail());
            req.setApc(request.getApc());
            req.setDiscount(request.getDiscount());
            req.setProjectParticipation(request.getProjectParticipation());
            req.setFundingRequested(request.getFundingRequested());
            req.setCurrency(request.getCurrency());
            req.setBankAccount(request.getBankAccount());
            if(request.getInvoice()!=null)
                req.setInvoice(invoiceManager.getInvoice(request.getInvoice()));
            req.setCoFunders(request.getCoFunders());
            req.setStatus(getStatus(request));
            req.setComments(requestDAO.getComments(request.getId()));
            req.setRequest(request);

            req.setApc_paid(request.getApc_paid());
            req.setTransfer_cost(request.getTransfer_cost());
            req.setOther_cost(request.getOther_cost());
            req.setDate_paid(request.getDate_paid());
        } catch (ManagerException me) {
            me.printStackTrace();
        }

        return req;
    }

    public boolean isSendCoordinatorEmails() {
        return sendCoordinatorEmails;
    }

    public void setSendCoordinatorEmails(boolean sendCoordinatorEmails) {
        this.sendCoordinatorEmails = sendCoordinatorEmails;
    }
}
