package de.hsadmin.mods.dom;

import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import de.hsadmin.core.model.AbstractEntity;
import de.hsadmin.core.model.AbstractModuleImpl;
import de.hsadmin.core.model.AuthorisationException;
import de.hsadmin.core.model.HSAdminException;
import de.hsadmin.mods.email.EMailAddress;
import de.hsadmin.mods.pac.Pac;
import de.hsadmin.mods.user.UnixUser;

public class DomainModuleImpl extends AbstractModuleImpl {
	
	@Override
	public AbstractEntity initialize(AbstractEntity newEntity) throws AuthorisationException {
		AbstractEntity newDom = super.initialize(newEntity);
		if (newDom instanceof Domain) {
			((Domain) newDom).setUser(getTransaction().getLoginUser());
			return newDom;
		}
		return null;
	}

	@Override
	public AbstractEntity find(Class<? extends AbstractEntity> entityClass, Object key) throws HSAdminException {
		// do query
		AbstractEntity res = super.find(entityClass, key);

		// check access rights
		needsReadAccessOn(res, "find");

		// return clean result
		return res;
	}

	@Override
	public AbstractEntity findByString(Class<? extends AbstractEntity> entityClass, String key)
			throws HSAdminException {
		// do query
		AbstractEntity res = super.findByString(entityClass, key);

		// check access rights
		if (res != null)
			needsReadAccessOn(res, "findByString");

		// return clean result
		return res;
	}

	@Override
	public List<AbstractEntity> search(Class<? extends AbstractEntity> entityClass, String condition, String orderBy) throws HSAdminException {
		// do query
		if (orderBy == null || orderBy.length() == 0) {
			orderBy = "ORDER BY obj.name ASC";
		}
		List<AbstractEntity> res = super.search(entityClass, condition, orderBy);
		List<AbstractEntity> ret = new LinkedList<AbstractEntity>();

		// remove entities where login user has no access rights
		for (AbstractEntity entity : res) {
			try {
				needsReadAccessOn(entity, "search");
				ret.add(entity);
			} catch (AuthorisationException exc) {
			} // ignore
		}

		// return clean result
		return ret;
	}

	@Override
	public AbstractEntity add(AbstractEntity newEntity) throws HSAdminException {
		Domain dom = (Domain) newEntity;
		Date now = new Date();
		dom.setSince(now);
		dom.setDnsMaster("dns.hostsharing.net");
		if (dom.getName() == null || dom.getName().length() == 0) {
			throw new HSAdminException("domain name required");
		}
		UnixUser admin = dom.getUser();
		if (admin == null || admin.getName() == null || admin.getName().length() == 0) {
			throw new HSAdminException("domain admin required");
		}
		// Standard domainoptions setzen. TODO: Alle defaults über eigene Klasse aus der Datenbank holen. 
		EntityManager em = getTransaction().getEntityManager();
		UnixUser loginUser = getTransaction().getLoginUser();
		if (!loginUser.hasHostmasterRole()) {
			// search for domains superior to dom
			Query domainQuery = em.createQuery("SELECT d FROM Domains d WHERE d.name = :domainName");
			String superior = dom.getName();
			while (superior.contains(".")) {
				if (dom.isPacDomain()) {
					break;
				}
				superior = superior.substring(superior.indexOf('.') + 1);
				domainQuery.setParameter("domainName", superior);
				List<?> resultList = domainQuery.getResultList();
				if (resultList.size() > 0) {
					Domain superDom = (Domain) resultList.get(0);
					if (superDom.isPacDomain()) {
						throw new HSAdminException("subdomains to pacdomain " + superDom.getName() + " are not allowed");
					}
					if (loginUser.hasPacAdminRoleFor(superDom.getUser().getPac())) {
						break;	// same pac
					}
					if (loginUser.hasCustomerRoleFor(superDom.getUser().getPac().getCustomer())) {
						break;	// same customer
					}
				}
			}
		}
		Query adminQuery = em.createQuery("SELECT u FROM UnixUsers u WHERE u.name = :adminName");
		adminQuery.setParameter("adminName", admin.getName());
		dom.setUser((UnixUser) adminQuery.getSingleResult());
		needsWriteAccessOn(newEntity, "add");
		em.persist(dom);
		Query q = em.createQuery("SELECT opt FROM " + 
				DomainOption.class.getAnnotation(javax.persistence.Entity.class).name() +
				" opt WHERE opt.name=:optName");
		HashSet<DomainOption> domainOptions = new HashSet<DomainOption>();
		q.setParameter("optName", "greylisting");
		List<?> list = q.getResultList();
		if (list.size() != 1) {
			throw new HSAdminException("invalid domain option: " + "greylisting");
		} else {
			domainOptions.add((DomainOption) list.get(0));
		}
		dom.setDomainoptions(domainOptions);
		if (dom.isPacDomain()) {
			em.persist(new EMailAddress("owner", "", dom, dom.getUser().getPac().getCustomer().getName().substring(6) + "@hostsharing.net"));
			em.persist(new EMailAddress("admin", "", dom, dom.getUser().getPac().getName() + "@hostsharing.net"));
			em.persist(new EMailAddress(dom.getUser().getPac().getName(), "", dom, dom.getUser().getPac().getName() + "@hostsharing.net"));
		} else {
			em.persist(new EMailAddress("abuse", "", dom, admin.getName()));
			em.persist(new EMailAddress("postmaster", "", dom, admin.getName()));
			em.persist(new EMailAddress("webmaster", "", dom, admin.getName()));
		}
		return super.add(dom);
	}

	@Override
	public AbstractEntity update(AbstractEntity existingEntity) throws HSAdminException {
		Domain updatedDom = (Domain) existingEntity;
		if (updatedDom.getName() == null || updatedDom.getName().length() == 0) {
			throw new HSAdminException("domain name required");
		}
		UnixUser loginUser = getTransaction().getLoginUser();
		EntityManager em = getTransaction().getEntityManager();
		Domain oldDom = em.find(Domain.class, updatedDom.getId());
		UnixUser admin = updatedDom.getUser();
		if (admin == null || admin.getName() == null || admin.getName().length() == 0) {
			throw new HSAdminException("domain admin required");
		}
		if (!admin.getName().equals(oldDom.getUser().getName())) {
			// TODO should be implemented later
			throw new AuthorisationException(loginUser, "update", existingEntity, "user");
		}
		Query q = em.createQuery("SELECT opt FROM " + 
				DomainOption.class.getAnnotation(javax.persistence.Entity.class).name() +
				" opt WHERE opt.name=:optName");
		Set<DomainOption> domainOptions = updatedDom.getDomainoptions();
		for (DomainOption opt : domainOptions) {
			q.setParameter("optName", opt.getName());
			List<?> list = q.getResultList();
			if (list.size() != 1) {
				throw new HSAdminException("invalid domain option: " + opt.getName());
			} else {
				opt.setId(((DomainOption) list.get(0)).getId());
			}
		}
		needsWriteAccessOn(existingEntity, "update");
		return super.update(existingEntity);
	}

	@Override
	public void delete(AbstractEntity existingEntity) throws HSAdminException {
		needsWriteAccessOn(existingEntity, "delete");
		Domain dom = (Domain) existingEntity;
		EntityManager em = getTransaction().getEntityManager();
		Query query = em.createQuery("SELECT adr FROM " + EMailAddress.class.getAnnotation(javax.persistence.Entity.class).name() 
				+ " adr WHERE adr.domain.name='" + dom.getName() + "'");
		List<?> resultList = query.getResultList();
		for (Object obj : resultList) {
			EMailAddress eMailAddress = (EMailAddress) obj;
			em.remove(eMailAddress);
		}
		super.delete(existingEntity);
	}

	private void needsReadAccessOn(AbstractEntity ent, String method) throws AuthorisationException {
		UnixUser loginUser = getTransaction().getLoginUser();
		if (ent instanceof Domain) {
			Domain dom = (Domain) ent;
			String aLoginUserName = loginUser.getName();
			UnixUser domUser = dom.getUser();
			Pac domPac = domUser.getPac();
			boolean isDomAdmin = aLoginUserName.equals(domUser.getName());
			boolean isPacAdmin = aLoginUserName.equals(domPac.getName());
			boolean isCustomer = aLoginUserName.equals(domPac.getCustomer().getName());
			boolean isHostmaster = loginUser.hasHostmasterRole();
			if (!isDomAdmin && !isPacAdmin && !isCustomer && !isHostmaster) {
				throw new AuthorisationException(loginUser, method, dom);
			}
		} else {
			throw new AuthorisationException(loginUser, method, ent);
		}
	}

	private void needsWriteAccessOn(AbstractEntity entity, String method) throws AuthorisationException {
		UnixUser loginUser = getTransaction().getLoginUser();
		if (entity instanceof Domain) {
			Domain dom = (Domain) entity;
			String aLoginUserName = loginUser.getName();
			UnixUser domUser = dom.getUser();
			Pac domPac = domUser.getPac();
			boolean isDomAdmin = aLoginUserName.equals(domUser.getName());
			boolean isPacAdmin = loginUser.hasPacAdminRoleFor(domPac);
			boolean isCustomer = aLoginUserName.equals(domPac.getCustomer().getName());
			boolean isHostmaster = loginUser.hasHostmasterRole();
			if (!isPacAdmin && !isCustomer && !isHostmaster) {
				if (!isDomAdmin || !"update".equals(method)) {
					throw new AuthorisationException(loginUser, method, dom);
				}
			}
			if (dom.isPacDomain() && !dom.getUser().getName().equals(domPac.getName())) {
				throw new AuthorisationException(loginUser, method, dom);
			}
			if (dom.isPacDomain() && !isHostmaster && !"add".equals(method)) {
				throw new AuthorisationException(loginUser, method, dom);
			}
		} else {
			throw new AuthorisationException(loginUser, method, entity);
		}
	}

}
