package de.hsadmin.core.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jms.Queue;
import javax.jms.QueueConnectionFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

import org.apache.openjpa.persistence.OpenJPAEntityManager;

import de.hsadmin.cliClientConnector.TechnicalException;
import de.hsadmin.core.qserv.QueueClient;
import de.hsadmin.core.qserv.QueueTask;
import de.hsadmin.core.util.Config;
import de.hsadmin.mods.cust.Customer;
import de.hsadmin.mods.pac.Pac;
import de.hsadmin.mods.user.UnixUser;

public class Transaction {

	private EntityManager entityManager;
	private QueueConnectionFactory queueConnectionFactory;
	private String loginName;
	private Map<String, QueueTaskStore> taskStores;
	private boolean transactionActive;
	private InitialContext ctx;
	
	public Transaction(String loginName) {
		transactionActive = false;
		this.entityManager = PersistenceManager.getEntityManager("hsadmin");
		this.loginName = loginName;
		taskStores = new HashMap<String, QueueTaskStore>();
		try {
			ctx = new InitialContext();
			Context env = (Context) ctx.lookup("java:comp/env");
			queueConnectionFactory = (QueueConnectionFactory) env.lookup("jms/QueueCF");
		} catch (NamingException e) {
			throw new TechnicalException("no jms queue: jms/QueueCF",  e);
		}
	}

	public EntityManager getEntityManager() {
		return entityManager;
	}
	
	public QueueConnectionFactory getQueueConnectionFactory() {
		return queueConnectionFactory;
	}
	
	public Queue lookupJMSQueue(String queueName) {
		if (ctx != null) {
			try {
				Context env = (Context) ctx.lookup("java:comp/env");
				return (Queue) env.lookup("jms/" + queueName);
			} catch (NamingException e) {
				throw new TechnicalException("no jms queue: jms/" + queueName, e);
			}
		}
		return null;
	}

	public String getLoginName() {
		if (loginName != null) {
			return loginName;
		}
		throw new TechnicalException("no login");
	}

	public void enqueue(String hiveName, QueueTask task) {
		QueueTaskStore taskStore = taskStores.get(hiveName);
		if (taskStore == null) {
			taskStore = new QueueTaskStore();
			taskStores.put(hiveName, taskStore);
		}
		taskStore.add(task);
	}
	
	private void sendAll(EntityTransaction transaction) {
		boolean firstHive = true;
		for (String hive : taskStores.keySet()) {
			QueueTaskStore store = taskStores.get(hive);
			String queueName = "hsadminSystem-" + hive;
			Queue jmsSystemQueue = lookupJMSQueue(queueName);
			QueueClient qClient = null;
			try {
				qClient = new QueueClient(queueConnectionFactory, jmsSystemQueue);
				if (firstHive) {
					firstHive = false;
					transaction.commit();
				}
				for (QueueTask task : store.getTasks()) {
					qClient.send(task);
				}
			} catch (Exception e) {
				throw new TechnicalException(e);
			} finally {
				store.clear();
				if (qClient != null) qClient.close();
			}
		}
		if (firstHive) {
			try {
				transaction.commit();
			} catch (Exception e) {
				throw new TechnicalException(e);
			}
		}
	}

	public void beginTransaction() {
		transactionActive = true;
		entityManager.getTransaction().begin();
	}

	public void commitTransaction() {
		sendAll(entityManager.getTransaction());
		transactionActive = false;
	}

	public void rollbackTransaction() {
		taskStores.clear();
		transactionActive = false;
		try {
			entityManager.getTransaction().rollback();
		} catch (IllegalStateException e) {
			// can't rollback 
		}
	}
	
	public void close() {
		if (transactionActive) {
			rollbackTransaction();
		}
		entityManager.close();
	}

	/**
	 * Detach entities from hibernate session. 
	 * Used to detach entities before update. Makes it possible to compare
	 * old and new attribute values. 
	 */
	public void detach(AbstractEntity entity) {
		if (entityManager instanceof OpenJPAEntityManager) {
			OpenJPAEntityManager openjpaEM = (OpenJPAEntityManager) entityManager;
			openjpaEM.detach(entity);
		}
	}

	class QueueTaskStore {
		private List<QueueTask> taskList;
		QueueTaskStore() {
			taskList = new ArrayList<QueueTask>();
		}
		public void clear() {
			taskList = new ArrayList<QueueTask>();
		}
		void add(QueueTask t) {
			taskList.add(t);
		}
		Iterable<QueueTask> getTasks() {
			return taskList;
		}
	}

	public UnixUser getLoginUser() {
		String loginName = getLoginName();
		if (loginName != null && loginName.length() == 2) {
			loginName = Config.getInstance().getProperty("accountprefix.hostmaster", "hsh01") + "-" + loginName;
		}
		if (loginName != null && loginName.length() == 3) {
			loginName = Config.getInstance().getProperty("accountprefix.customer", "hsh00") + "-" + loginName;
		}
		Query userQuery = getEntityManager().createQuery("SELECT u FROM UnixUsers u WHERE u.name = :username");
		userQuery.setParameter("username", loginName);
		UnixUser unixUser = (UnixUser) userQuery.getSingleResult();
		return unixUser;
	}

	public boolean login(String user, String ticket) throws AuthenticationException {
		String ticketUser = TicketValidator.getInstance().validateTicket(ticket);
		if (user != null && user.equals(ticketUser)) {
			return true;		// user himself
		}
		if (ticketUser != null && ticketUser.length() == 2) {
			return true;		// 2-letter hostmaster
		}
		String hostmasterAccountPrefix = Config.getInstance().getProperty("accountprefix.hostmaster", "hsh01") + "-";
		if (ticketUser != null && ticketUser.startsWith(hostmasterAccountPrefix) && ticketUser.length() == 8) {
			return true;		// hsh01 hostmaster
		}
		if (ticketUser != null && ticketUser.length() == 5) {
			Query userQuery = getEntityManager().createQuery("SELECT u FROM UnixUsers u WHERE u.name = :username");
			userQuery.setParameter("username", user);
			UnixUser unixUser = (UnixUser) userQuery.getSingleResult();
			String pacName = unixUser.getPac().getName();
			return ticketUser.equals(pacName);  // pac-admin
		}
		String memberAccountPrefix = Config.getInstance().getProperty("accountprefix.customer", "hsh00") + "-";
		if (ticketUser != null && (ticketUser.length() == 3 || (ticketUser.length() >= 9 && ticketUser.startsWith(memberAccountPrefix)))) {
			Query memberQuery = getEntityManager().createQuery("SELECT c FROM Customers c WHERE c.name = :membername");
			memberQuery.setParameter("membername", ticketUser.length() == 3 ? (memberAccountPrefix + ticketUser) : ticketUser);
			Customer member = (Customer) memberQuery.getSingleResult();
			Set<Pac> pacs = member.getPacs();
			for (Pac p : pacs) {
				if (p.getName().equals(user)) {
					return true;  // member as pac-admin
				}
				Set<UnixUser> users = p.getUnixUser();
				for (UnixUser u : users) {
					if (u.getName().equals(user)) {
						return true; // member as pac-user
					}
				}
			}
		}
		throw new AuthenticationException("User " + ticketUser + " is not allowed to run as " + user);
	}

}
