package de.hsadmin.mods.user;

import java.util.LinkedList;
import java.util.List;

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

import de.hsadmin.core.model.AuthorisationException;
import de.hsadmin.core.model.AbstractEntity;
import de.hsadmin.core.model.AbstractModuleImpl;
import de.hsadmin.core.model.HSAdminException;
import de.hsadmin.mods.pac.Pac;

public class UnixUserModuleImpl extends AbstractModuleImpl {

	private static final long serialVersionUID = 7480353553685400790L;

	public UnixUserModuleImpl() {
	}

	@Override
	public AbstractEntity initialize(AbstractEntity newEntity) throws AuthorisationException {
		UnixUser newUnixUser = (UnixUser) super.initialize(newEntity);
		newUnixUser.setName(getLoginUser().getPac().getName() + '-');
		return newUnixUser;
	}

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

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

		// return clean result
		return res;
	}

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

		// return directly (checking rights already done in search within
		// findByString)
		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
		if (res != null) {
			for (AbstractEntity entity : res) {
				try {
					UnixUser returnedUnixUser = (UnixUser) entity;
					needsPartialAccessOnPacOf(returnedUnixUser, "search");
					ret.add(returnedUnixUser);
				} catch (AuthorisationException exc) {
				} // ignore
			}
		}

		// return clean result
		return ret;
	}

	@Override
	public AbstractEntity add(AbstractEntity newEntity) throws HSAdminException {
		EntityManager em = getEntityManager();
		// only allow pac which matches the username (TODO: hard coded
		// Hostsharing convention)
		UnixUser newUnixUser = (UnixUser) newEntity;
		// validation of username and password
		String name = newUnixUser.getName();
		if (name == null) {
			throw new HSAdminException("username is required");
		}
		String userName = name.toLowerCase().trim();
		for (char c : userName.toCharArray()) {
			if (!(Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == '_')) {
				throw new AuthorisationException(getLoginUser(), "add", newUnixUser, "userId");
			}
		}
		if (userName.length() < 7 || userName.charAt(5) != '-' || userName.lastIndexOf('-') > 5) {
			throw new AuthorisationException(getLoginUser(), "add", newUnixUser, "userId");
		}
		String passWord = newUnixUser.getPassword();
		if (passWord == null || passWord.length() == 0) {
			throw new HSAdminException("password is required");
		}
		if (passWord.indexOf(':') >= 0) {
			throw new AuthorisationException(getLoginUser(), "add", newUnixUser, "userId");
		}
		if (newUnixUser.getPac() == null || newUnixUser.getPac().getNew()) {
			
		}
		Query qPac = em.createQuery("SELECT obj FROM Pacs obj WHERE obj.name = :pacName");
		qPac.setParameter("pacName", userName.substring(0, 5));
		Object singleResult = qPac.getSingleResult();
		Pac pac = (Pac) singleResult;
		newUnixUser.setName(userName);
		newUnixUser.setHomedir("/home/pacs/" + userName.substring(0, 5) + "/users/" + userName.substring(6));

		// no appropriate uid?
		if (newUnixUser.getUserId() < 1000) {
			// determine next free uid
			long nUID = 20000;
			Query qUID = em.createQuery("SELECT MAX(u.userId) FROM UnixUsers u");
			Long maxUid = (Long) qUID.getSingleResult();
			if (maxUid >= nUID)
				nUID = maxUid + 1;
			newUnixUser.setUserId(nUID);
		} else {
			// given uid belongs to same pac?
			Query q = em.createQuery("SELECT u.userId FROM UnixUsers u WHERE u.userId = :userId AND u.pac = :pac");
			q.setParameter("userId", newUnixUser.getUserId());
			q.setParameter("pac", pac);
			List<?> idOfSamePac = q.getResultList();
			if (idOfSamePac.size() == 0) {
				throw new AuthorisationException(getLoginUser(), "add", newUnixUser, "userId");
			}
		}

		// don't move this up, it will update the new entity still with wrong
		// userid!
		newUnixUser.setPac(pac);
		newUnixUser.init();

		// authorisation check
		needsFullAccessOnPacOf(newUnixUser, "add");

		// add new entity
		return super.add(newEntity);
	}

	@Override
	public AbstractEntity update(AbstractEntity existingEntity) throws HSAdminException {
		// get the entity from the database
		UnixUser detachedUnixUser = (UnixUser) existingEntity;
		UnixUser attachedUnixUser = getEntityManager().find(detachedUnixUser.getClass(),
				detachedUnixUser.getId());

		// authorisation check
		needsFullAccessOnUser(attachedUnixUser, "update");

		// update fields where the login user has write access
		if (attachedUnixUser.getUserId() != detachedUnixUser.getUserId())
			throw new AuthorisationException(getLoginUser(), "update",
					detachedUnixUser, "id");
		attachedUnixUser.setName(detachedUnixUser.getName());
		attachedUnixUser.setPassword(detachedUnixUser.getPassword());
		if (hasFullAccessOnPacOf(attachedUnixUser))
			attachedUnixUser.setComment(detachedUnixUser.getComment());
		else if (!attachedUnixUser.getComment().equals(
				detachedUnixUser.getComment()))
			throw new AuthorisationException(getLoginUser(), "update",
					detachedUnixUser, "comment");
		if (hasFullAccessOnPacOf(attachedUnixUser))
			attachedUnixUser.setHomedir(detachedUnixUser.getHomedir());
		else if (!attachedUnixUser.getHomedir().equals(
				detachedUnixUser.getHomedir()))
			throw new AuthorisationException(getLoginUser(), "update",
					detachedUnixUser, "homedir");
		if (!attachedUnixUser.getShell().equals(detachedUnixUser.getShell()))
			if (hasFullAccessOnPacOf(attachedUnixUser)
					|| isLoginShell(attachedUnixUser.getShell())
					|| !isLoginShell(detachedUnixUser.getShell()))
				attachedUnixUser.setShell(detachedUnixUser.getShell());
			else
				throw new AuthorisationException(getLoginUser(), "update",
						detachedUnixUser, "shell");
		if (attachedUnixUser.isLocked() != detachedUnixUser.isLocked())
			throw new AuthorisationException(getLoginUser(), "update",
					detachedUnixUser, "locked");
		if (detachedUnixUser.getQuotaSoftlimit() != null) {
			if (hasFullAccessOnPacOf(attachedUnixUser)) {
				attachedUnixUser.setQuotaSoftlimit(detachedUnixUser.getQuotaSoftlimit());
				if (detachedUnixUser.getQuotaHardlimit() != null) {
					attachedUnixUser.setQuotaHardlimit(detachedUnixUser.getQuotaHardlimit());
				}
			}
			else {
				Integer oldQuota = attachedUnixUser.getQuotaSoftlimit();
				Integer newQuota = detachedUnixUser.getQuotaSoftlimit();
				if (oldQuota != newQuota && !oldQuota.equals(newQuota))
					throw new AuthorisationException(getLoginUser(), "update",
							detachedUnixUser, "quota");
			}
		}

		// update entity
		return super.update(attachedUnixUser);
	}

	@Override
	public void delete(AbstractEntity existingEntity) throws HSAdminException {
		// get the entity from the database
		UnixUser detachedUnixUser = (UnixUser) existingEntity;
		UnixUser attachedUnixUser = getEntityManager().find(detachedUnixUser.getClass(),
				detachedUnixUser.getId());

		// authorisation check
		if (attachedUnixUser.getName().length() < 7) {
			throw new AuthorisationException(attachedUnixUser, "delete");
		}
		needsFullAccessOnPacOf(attachedUnixUser, "delete");

		// delete entity
		super.delete(attachedUnixUser);
	}

	private EntityManager getEntityManager() {
		return getTransaction().getEntityManager();
	}

	// throws an AuthorisationException if the login user has no write acess
	// on the pac of the given UnixUser
	private boolean hasFullAccessOnPacOf(UnixUser user) {
		// only pac admins (same name as pac) and the owner (customer) have
		// write access to the pac
		boolean isPacAdmin = getLoginUser().getName().equals(
				user.getPac().getName());
		boolean isCustomer = getLoginUser().getName().equals(
				user.getPac().getCustomer().getName());
		boolean isHostmaster = getLoginUser().hasHostmasterRole();
		return isPacAdmin || isCustomer || isHostmaster;
	}

	// throws an AuthorisationException if the login user has no write acess
	// on the pac of the given UnixUser
	private void needsFullAccessOnPacOf(UnixUser user, String method)
			throws AuthorisationException {
		if (!hasFullAccessOnPacOf(user))
			throw new AuthorisationException(getLoginUser(), method, user);
	}

	// throws an AuthorisationException if the login user has no read acess on
	// the pac of the given UnixUser
	private void needsPartialAccessOnPacOf(UnixUser user, String method)
			throws AuthorisationException {
		if (!hasFullAccessOnPacOf(user)
				&& getLoginUser().getPac().id() != user.getPac().id())
			throw new AuthorisationException(getLoginUser(), method, user);
	}

	// throws an AuthorisationException if the login user has not even partial
	// write access on the given UnixUser
	private void needsFullAccessOnUser(UnixUser user, String method)
			throws AuthorisationException {
		// neither pac admin (same name as pac), pac owner (customer) nor the
		// user itself?
		if (!hasFullAccessOnPacOf(user) && !getLoginUser().sameIdAs(user))
			throw new AuthorisationException(getLoginUser(), method, user);
	}

	// returns true if the given shell is a login shell
	private static boolean isLoginShell(String shell) {
		// TODO: list of login shells should not be hardcoded
		if (shell.equals("/bin/sh"))
			return true;
		if (shell.equals("/bin/bash"))
			return true;
		if (shell.equals("/bin/csh"))
			return true;
		if (shell.equals("/bin/tcsh"))
			return true;
		if (shell.equals("/bin/zsh"))
			return true;
		if (shell.equals("/bin/ksh"))
			return true;
		return false;
	}

}
