package eu.dnetlib.goldoa.service;

import eu.dnetlib.goldoa.domain.Budget;
import eu.dnetlib.goldoa.domain.BudgetInfo;
import eu.dnetlib.goldoa.domain.Comment;
import eu.dnetlib.goldoa.domain.ManagerException;
import eu.dnetlib.goldoa.domain.OrganizationManagerException;
import eu.dnetlib.goldoa.domain.Person;
import eu.dnetlib.goldoa.domain.PersonManagerException;
import eu.dnetlib.goldoa.service.dao.BudgetDAO;
import eu.dnetlib.goldoa.service.utils.EmailUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;

/**
 * Created by antleb on 4/2/15.
 */
@Transactional
public class BudgetManagerImpl implements BudgetManager {

	@Autowired private BudgetDAO budgetDAO;
	@Autowired private PersonManager personManager;
	@Autowired private OrganizationManager organizationManager;
	@Autowired private PublisherManager publisherManager;
	@Autowired private InvoiceManager invoiceManager;
	@Autowired private ExecutorService executorService;
	@Autowired private EmailUtils emailUtils;

	@Override
	public Budget saveBudget(final Budget budget) {
		if (budget.getId() == null) {
//			budget.setId(UUID.randomUUID().toString());

			budget.setId("B-" + new SimpleDateFormat("yyyyMMdd-").format(new Date()) + budgetDAO.getRequestId());

			budget.setDate(new Date());
			budget.setRemaining(budget.getAmountGranted());
		}

		budgetDAO.saveBudget(budget);

		return budget;
	}

	@Override
	public Budget submitBudgetRequest(final Budget budget) {
		budget.setStatusCode(Budget.Status.SUBMITTED.getCode());

		saveBudget(budget);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudgetInfo(budget);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserNewBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorNewBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});

		return budget;
	}

	@Override
	public BudgetInfo getBudget(String budgetId) {
		return getBudgetInfo(budgetDAO.getBudget(budgetId));
	}

	@Override
	public List<BudgetInfo> getBudgets() {
		List<BudgetInfo> res = new ArrayList<BudgetInfo>();

		for (Budget budget : budgetDAO.getBudgets())
			res.add(getBudgetInfo(budget));

		return res;
	}

	@Override
	public List<BudgetInfo> getBudgetsForUser(String userId) {
		List<BudgetInfo> res = new ArrayList<BudgetInfo>();

		for (Budget budget : budgetDAO.getBudgetsForUser(userId))
			res.add(getBudgetInfo(budget));

		return res;
	}

	@Override
	public List<BudgetInfo> getBudgetsForPublisher(String publisherId) {
		List<BudgetInfo> res = new ArrayList<BudgetInfo>();

		for (Budget budget : budgetDAO.getBudgetsForPublisher(publisherId))
			res.add(getBudgetInfo(budget));

		return res;
	}

	@Override
	public List<BudgetInfo> getBudgetsForAccounting() {
		List<BudgetInfo> res = new ArrayList<BudgetInfo>();

		for (Budget budget : budgetDAO.getBudgetsForAccounting())
			res.add(getBudgetInfo(budget));

		return res;
	}

	@Override
	public List<BudgetInfo> getBudgetsForOrganization(List<String> organizationIds) {
		List<BudgetInfo> res = new ArrayList<BudgetInfo>();

		for (Budget budget : budgetDAO.getBudgetsForOrganization(organizationIds))
			res.add(getBudgetInfo(budget));

		return res;
	}

	@Override
	public void initiallyApproveBudgetRequest(final String budgetId, String comment, String personId) {
		budgetDAO.initiallyApproveBudget(budgetId);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserAcceptedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorAcceptedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void approveBudgetRequest(final String budgetId, String comment, float amountGranted, String personId) {
		budgetDAO.approveBudget(budgetId, amountGranted);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserAcceptedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorAcceptedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void rejectBudgetRequest(final String budgetId, String comment, String personId) {
		budgetDAO.rejectBudget(budgetId);
//		budgetDAO.updateBudgetComment(budgetId, comment);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserRejectedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRejectedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void processingPayment(final String budgetId, String comment, String personId) {
		budgetDAO.processingBudget(budgetId);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserRejectedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRejectedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void denyPayment(final String budgetId, String comment, String personId) {
		budgetDAO.accountingDeniedBudget(budgetId);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserRejectedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRejectedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void onHoldPayment(final String budgetId, String comment, String personId) {
		budgetDAO.accountingOnHoldBudget(budgetId);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserRejectedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRejectedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void budgetPaid(final String budgetId, String comment, String personId) {
		budgetDAO.accountingPaidBudget(budgetId);
		budgetDAO.saveComment(budgetId, personId, comment, null);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					BudgetInfo budgetInfo = getBudget(budgetId);
					List<Person> moderators = personManager.getModerators();

					emailUtils.sendUserRejectedBudgetEmail(budgetInfo);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRejectedBudgetEmail(moderator, budgetInfo);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void uploadInitialContract(String budgetId, String contentType, InputStream inputStream) {
		budgetDAO.uploadInitialContract(budgetId, contentType, inputStream);
	}

	@Override
	public void uploadBeneficiaryContract(String budgetId, String contentType, InputStream inputStream) {
		budgetDAO.uploadBeneficiaryContract(budgetId, contentType, inputStream);
	}

	@Override
	public void uploadSignedContract(String budgetId, String contentType, InputStream inputStream) {
		budgetDAO.uploadSignedContract(budgetId, contentType, inputStream);
	}

	@Override
	public void uploadBankReceipt(String budgetId, String contentType, InputStream inputStream) {
		budgetDAO.uploadBankReceipt(budgetId, contentType, inputStream);
	}

	@Override
	public BudgetInfo getForRequest(String organizationId, String publisherId) {
		List<BudgetInfo> budgets;
		BudgetInfo res = null;

		if (organizationId != null) {
			budgets = getBudgetsForOrganization(Arrays.asList(organizationId));

			for (BudgetInfo budgetInfo : budgets) {
				if (isValid(budgetInfo)) {
					res = budgetInfo;

					break;
				}
			}
		} else if (publisherId != null) {
			budgets = getBudgetsForPublisher(publisherId);

			for (BudgetInfo budgetInfo : budgets) {
				if (isValid(budgetInfo)) {
					res = budgetInfo;

					break;
				}
			}
		}

		return res;
	}

	private boolean isValid(BudgetInfo budgetInfo) {
		//TODO fix me!
		return budgetInfo.getStatus().equals(Budget.Status.ACCOUNTING_PAID);
	}


	private BudgetInfo getBudgetInfo(Budget budget) {
		BudgetInfo budgetInfo = new BudgetInfo();

		budgetInfo.setId(budget.getId());
		budgetInfo.setDate(budget.getDate());
		budgetInfo.setStartDate(budget.getStartDate());
		budgetInfo.setEndDate(budget.getEndDate());
		budgetInfo.setAmountGranted(budget.getAmountGranted());
		budgetInfo.setAmountRequested(budget.getAmountRequested());
		budgetInfo.setRemaining(budget.getRemaining());
		budgetInfo.setCurrency(budget.getCurrency());
		budgetInfo.setStatusCode(budget.getStatusCode());
		budgetInfo.setStatus(getStatus(budget.getStatusCode()));
		budgetInfo.setComments(budgetDAO.getBudgetComments(budget.getId()));
		budgetInfo.setBankAccount(budget.getBankAccount());

		if (budget.getUser() != null) {
			try {
				budgetInfo.setUser(personManager.getById(budget.getUser()));
			} catch (PersonManagerException e) {
				e.printStackTrace();
			}
		}

		if (budget.getOrganisation() != null) {
			try {
				budgetInfo.setOrganisation(organizationManager.getById(budget.getOrganisation()));
			} catch (OrganizationManagerException e) {
				e.printStackTrace();
			}
		}

		if (budget.getPublisher() != null) {
			budgetInfo.setPublisher(publisherManager.getPublisher(budget.getPublisher()));
		}

		if (budget.getInvoice() != null)
			try {
				budgetInfo.setInvoice(invoiceManager.getInvoice(budget.getInvoice()));
			} catch (ManagerException e) {
				e.printStackTrace();
			}

		for (Comment c: budgetInfo.getComments()) {
			try {
				c.setPerson(personManager.getById(c.getPerson().getId()));
			} catch (PersonManagerException e) {
				e.printStackTrace();
			}
		}

		return budgetInfo;
	}

	private Budget.Status getStatus(int statusCode) {
		return Budget.Status.forStatus(statusCode);
	}
}
