package info.bukova.isspst.services.requirement;

import info.bukova.isspst.Constants;
import info.bukova.isspst.data.AuthItem;
import info.bukova.isspst.data.JobMapping;
import info.bukova.isspst.data.RequirementBase;
import info.bukova.isspst.data.RequirementState;
import info.bukova.isspst.data.Role;
import info.bukova.isspst.data.SettingsData;
import info.bukova.isspst.data.User;
import info.bukova.isspst.data.Workflow;
import info.bukova.isspst.data.Workgroup;
import info.bukova.isspst.mail.MailMessage;
import info.bukova.isspst.mail.Mailer;
import info.bukova.isspst.mail.MessageBuilder;
import info.bukova.isspst.services.AbstractOwnedService;
import info.bukova.isspst.services.LazyLoader;
import info.bukova.isspst.services.settings.GlobalSettingsService;
import info.bukova.isspst.services.users.UserService;
import info.bukova.isspst.services.workgroups.WorkgroupService;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.hibernate.LazyInitializationException;
import org.hibernate.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Pepa Rokos
 * @author Franta Přibyl
 *
 * Abstraktní bázová třída všech požadavků
 *
 * @param <T> Třída požadavku
 */
public abstract class RequirementBaseServiceImpl<T extends RequirementBase> extends
		AbstractOwnedService<T> implements RequirementBaseService<T> {
	
	@Autowired
	private Mailer mailer;
	@Autowired
	private MessageBuilder messageBuilder;
	@Autowired
	private GlobalSettingsService settingsService;
	@Autowired
	private UserService userService;
	@Autowired
	protected WorkgroupService workgroupService;

	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_ADD')")
	public void add(T entity) {
		Workgroup reqWorkgroup = null;
		for (Workgroup w : workgroupService.getUserWorkgroups(getLoggedInUser())) {
			if (workgroupService.getMembers(entity.getCentre()).contains(w)) {
				reqWorkgroup = w;
			}
		}
		
		entity.setWorkgroup(reqWorkgroup);
			
		checkEnable();
		
		if (!canAdd(entity)) {
			throw new AddRequirementException();
		}
		
		this.addWorkflow(entity);
		entity.setNumser(this.getNumberSerie());
		
		super.add(entity);
		
		if (canApprove(entity)) {
			approve(entity);
		} else {
			this.sendToApprovers(entity);
		}
	}
	
	private void checkEnable() {
		if (!settingsService.getSettings().isEnableRequirements()) {
			throw new AddRequirementException("RequirementsDisabled");
		}
	}
	
	protected boolean canAdd(T entity) {
		return true;
	}
	
	private void sendToApprovers(T entity) {
		SettingsData settings = settingsService.getSettings();
		List<User> approvers = this.getNextApprover(entity);
		if (approvers != null && !approvers.isEmpty()) {
			MailMessage message = messageBuilder.buildMessage(settings.getNewReqTemplate(), entity);
			message.setTo(userService.getEmailsForSend(approvers));
			
			mailer.send(message);
		}
	}

	protected void addWorkflow(T entity) {
		if (entity.getType() == null) {
			return;
		}
		
		for (Workflow w : entity.getType().getWorkflow()) {
			if ((entity.getWorkgroup() != null && !w.getCentre()) || w.getCentre()) {
				Workflow workflow = new Workflow();
				workflow.setCentre(w.getCentre());
				workflow.setLimit(w.getLimit());
				workflow.setOrder(w.getOrder());
				workflow.setRole(w.getRole());
				entity.getWorkflow().add(workflow);
			}
		}
	}

	@Override
	@Transactional
	public List<User> getNextApprover(T entity) {
		T e = (T) dao.getById(entity.getId());
		
		List<User> users = new ArrayList<User>();
		Workgroup wg;
		Workflow wf = getNextWorkflow(e);
		Role nextRole;
		
		if (wf == null) {
			return null;
		} else {
			nextRole = wf.getRole();
		}
		
		if (wf.getCentre() || e.getWorkgroup() == null) {
			wg = e.getCentre();
		} else {
			wg = e.getWorkgroup();
		}
		
		workgroupService.loadMembers(wg);
		
		for (JobMapping jm : wg.getMembers())
		{
			if (jm.getRole().equals(nextRole) && jm.getMember().isPerson()) {
				users.add((User) jm.getMember());
			}
		}
		
		return users;
	}

	@Override
	@Transactional
	@LazyLoader({"grid", "form"})
	public void loadAuthItems(T entity) {
		try {
			if (entity.getAuthorization() == null) {
				throw new LazyInitializationException("");
			}
			entity.getAuthorization().size();
		} catch (LazyInitializationException ex) {
			T e = dao.getById(entity.getId());
			e.getAuthorization().size();
			entity.setAuthorization(e.getAuthorization());
		}
	}
	

	@Override
	@Transactional
	@LazyLoader("grid")
	public void loadType(T data) {
		try {
			if (data.getType() == null) {
				throw new LazyInitializationException("");
			}
			data.getType().getType();
		} catch (LazyInitializationException ex) {
			T e = dao.getById(data.getId());
			
			if (e.getType() == null) {
				return;
			}
			
			e.getType().getType();
			data.setType(e.getType());
		}
	}

	@Override
	@Transactional
	@LazyLoader({"grid", "form"})
	public void loadWorkflow(T data) {
		try {
			if (data.getWorkflow() == null) {
				throw new LazyInitializationException("");
			}
			data.getWorkflow().size();
		} catch (LazyInitializationException ex) {
			T e = dao.getById(data.getId());
			e.getWorkflow().size();
			e.getOwnedBy().getFullName();
			data.setWorkflow(e.getWorkflow());
			data.setOwnedBy(e.getOwnedBy());
		}
	}

	protected void approve(T entity, User user) {
		T e = (T) dao.getById(entity.getId());
		
		Workflow wf = getNextWorkflow(e);
		if (wf == null) {
			return;
		}
		
		Role role = wf.getRole();
		AuthItem auth = new AuthItem();
		auth.setApprover(user);
		auth.setRole(role);
		auth.setAuthDate(new Date());
		
		e.getAuthorization().add(auth);
		
		if (getNextWorkflow(e) == null) {
			e.setState(RequirementState.APPROVED);
		} else {
			e.setState(RequirementState.PARTIALLY);
		}
		
		super.update(e);
		
		if (!autoApprove(e)) {
			this.sendToApprovers(e);
			
			SettingsData settings = settingsService.getSettings();
			MailMessage message = null;
				
			if (e.getOwnedBy().getEmail() != null 
					&& !e.getOwnedBy().getEmail().isEmpty()
					&& e.getOwnedBy().isNotify()) {
				if (e.getState() == RequirementState.APPROVED && settings.getConfReqTemplate() != null) {
					message = messageBuilder.buildMessage(settings.getConfReqTemplate(), e);
				} else if (settings.getAuthReqTemplate() != null) {
					message = messageBuilder.buildMessage(settings.getAuthReqTemplate(), e);
				}
				
				if (message != null) {
					message.setFrom(getLoggedInUser().getEmail());
					message.setTo(e.getOwnedBy().getEmail());
					mailer.send(message);	
				}
			}
		}
		
		postApprove(e);
	}
	
	@Override
	@Transactional
	@PreAuthorize("this.canApprove(#entity)")
	public void approve(T entity) {
		approve(entity, getLoggedInUser());
	}

	/**
	 * Volá se z metody approve pro automatické schválení dalším schvalovatelem v pořadí. Metoda z báze nedělá nic.
	 * 
	 * @param entity Požadavek
	 * @return true pokud se provedlo automatické schválení.
	 */
	protected boolean autoApprove(T entity) {
		return false;
	}
	
	/**
	 * Činnost prováděná po schválení požadavku (i dílčím schválení).
	 * 
	 * @param entity Požadavek
	 */
	protected void postApprove(T entity) {
		
	}

	@Override
	@Transactional
	public boolean canApprove(T entity) {
		List<User> nextApprovers = getNextApprover(entity); 
		if (nextApprovers != null && nextApprovers.contains(getLoggedInUser())) {
			return true;
		}
		
		return false;
	}
	
	protected Workflow getNextWorkflow(T e) {
		AuthItem authItem = null;
		if (e.getWorkflow() == null) {
			return null;
		}
		
		if (e.getAuthorization().size() > 0) {
			authItem = e.getAuthorization().get(e.getAuthorization().size() - 1); 
		}
		
		Workflow wf = null;
		for (int i = 0; i < e.getWorkflow().size(); i++) {
			wf = e.getWorkflow().get(i);
			if (authItem != null && wf.getRole().equals(authItem.getRole())) {
				if (i + 1 < e.getWorkflow().size()) {
					wf = e.getWorkflow().get(i + 1);
					break;
				} else {
					return null;
				}
			}
			
			if (authItem == null) {
				break;
			}
		}
		
		return wf;
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_READ')")
	public List<T> getMy()
	{
		Query q = dao.getQuery("from " + dao.getEntityName() + " where ownedBy = :owner and state != :state");
		q.setParameter("owner", getLoggedInUser());
		q.setParameter("state", RequirementState.APPROVED);
		return q.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_SHOW_CENTRE_REQ')")
	@PostFilter("hasPermission(filterObject, 'PERM_SHOW_CENTRE_REQ')")
	public List<T> getCentreReq()
	{
		List<Workgroup> wgList = workgroupService.getUserCentres(getLoggedInUser());
		Query q = dao.getQuery("select tr from " + dao.getEntityName() + " tr join fetch tr.ownedBy join tr.centre c where tr.state != :state and c in (:wgList) order by tr.numser");
		q.setParameterList("wgList", wgList);
		q.setParameter("state", RequirementState.APPROVED);
		return q.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_SHOW_WORKGROUP_REQ')")
	@PostFilter("hasPermission(filterObject, 'PERM_SHOW_WORKGROUP_REQ')")
	public List<T> getWorkgroupReq()
	{
		List<Workgroup> wgList = workgroupService.getUserWorkgroups(getLoggedInUser());
		Query q = dao.getQuery("select tr from " + dao.getEntityName() + " tr join fetch tr.ownedBy join tr.workgroup w where tr.state != :state and w in (:wgList) order by tr.numser");
		q.setParameterList("wgList", wgList);
		q.setParameter("state", RequirementState.APPROVED);
		return q.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_SHOW_ALL_REQ')")
	public List<T> getAll()
	{
		Query q = dao.getQuery("from " + dao.getEntityName() + " as tr join fetch tr.ownedBy order by tr.numser");
		return q.list();
	}

	@Override
	public String getUpdateEntityPermission() {
		return Constants.PERM_EDIT_NEW;
	}
	
	@Override
	public String getDeleteEntityPermission() {
		return Constants.PERM_DELETE_NEW;
	}

}
