package de.hsadmin.mods.dom;

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

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

import de.hsadmin.core.model.AbstractEntity;
import de.hsadmin.core.qserv.CompoundProcessor;
import de.hsadmin.core.qserv.CopyFileProcessor;
import de.hsadmin.core.qserv.CreateFileProcessor;
import de.hsadmin.core.qserv.EntityProcessorFactory;
import de.hsadmin.core.qserv.Processor;
import de.hsadmin.core.qserv.ProcessorException;
import de.hsadmin.core.qserv.ShellProcessor;
import de.hsadmin.core.qserv.VelocityProcessor;
import de.hsadmin.core.qserv.WaitingTasksProcessor;
import de.hsadmin.core.util.Config;
import de.hsadmin.mods.email.EMailAddress;
import de.hsadmin.mods.email.EMailAddressProcessorFactory;
import de.hsadmin.mods.pac.Pac;
import de.hsadmin.mods.user.UnixUser;

public class DomainProcessorFactory implements EntityProcessorFactory {
	
	private static final String[] DYNAMIC_STRUCTURE = new String[] { 
		"htdocs", "htdocs-ssl", "subs", "subs/www", "subs-ssl", "subs-ssl/www", "cgi", "fastcgi", "cgi-ssl", "fastcgi-ssl", "etc", "var" 
	};
	
	private static final String[] STATIC_STRUCTURE = new String[] { 
		"htdocs", "htdocs-ssl", "subs", "subs/www", "subs-ssl", "subs-ssl/www", "etc", "var" 
	};

	public <T extends AbstractEntity> Processor createCreateProcessor(EntityManager em, T entity) throws ProcessorException {
		String hiveName = entity.getHiveName();
		Domain dom = (Domain) entity;
		UnixUser domUser = dom.getUser();
		Pac pac = domUser.getPac();
		WaitingTasksProcessor mainProcessor = new WaitingTasksProcessor(createHiveDNSSetupProcessor(em, dom));
		mainProcessor.appendProcessor(hiveName, createHiveEMailSetupProcessor(em, dom), "Setup EMail");
		Config config = Config.getInstance();
		for (String queueName : config.getProperty("queues.dns").split(",")) {
			mainProcessor.appendProcessor(queueName, createDNSServerConfigProcessor(em), queueName + ".hostsharing.net");
		}
		for (String queueName : config.getProperty("queues.mail").split(",")) {
			mainProcessor.appendProcessor(queueName, createMailinSetupProcessor(em, dom, pac), queueName + ".hostsharing.net");
		}
		mainProcessor.appendProcessor(hiveName, createDomainDirectoriesProcessor(dom), "Setup Domain Directories");
		mainProcessor.appendProcessor(hiveName, createApacheVHostSetupProcessor(em, dom), "Setup Apache VHost");
		return mainProcessor;
	}

	public <T extends AbstractEntity> Processor createUpdateProcessor(EntityManager em, T entity) throws ProcessorException {
		Domain dom = (Domain) entity;
		UnixUser domUser = dom.getUser();
		Pac pac = domUser.getPac();
		WaitingTasksProcessor processor = new WaitingTasksProcessor(createApacheVHostSetupProcessor(em, dom));
		Config config = Config.getInstance();
		for (String queueName : config.getProperty("queues.mail").split(",")) {
			processor.appendProcessor(queueName, createMailinSetupProcessor(em, dom, pac), queueName + ".hostsharing.net");
		}
		return processor;
	}

	public <T extends AbstractEntity> Processor createDeleteProcessor(EntityManager em, T entity) throws ProcessorException {
		Domain dom = (Domain) entity;
		String domName = dom.getName();
		WaitingTasksProcessor mainProcessor = new WaitingTasksProcessor(						 
				createHiveDNSRemoveProcessor(domName)
			);
		Config config = Config.getInstance();
		for (String queueName : config.getProperty("queues.dns").split(",")) {
			mainProcessor.appendProcessor(queueName, createDNSServerConfigProcessor(em), queueName + ".hostsharing.net");
		}
		for (String queueName : config.getProperty("queues.mail").split(",")) {
			mainProcessor.appendProcessor(queueName, createMailinUnsetupProcessor(em, dom), queueName + ".hostsharing.net");
		}
		mainProcessor.appendProcessor(dom.getHiveName(), createApacheVHostDeleteProcessor(dom), "remove apache vhost");
		return mainProcessor;
	}

	private Processor createDNSServerConfigProcessor(EntityManager em) {
		Query query = em.createQuery("SELECT d FROM Domains d");		
		HashMap<String, Object> templateVars = new HashMap<String, Object>();
		templateVars.put("domains", query.getResultList());
		return new CompoundProcessor(
			new VelocityProcessor("/de/hsadmin/mods/dom/named-hsh-conf.vm",
						templateVars, "/etc/bind/named-hsh.conf.tmp", true),
			new ShellProcessor(" ( diff -q /etc/bind/named-hsh.conf.tmp /etc/bind/named-hsh.conf && rm /etc/bind/named-hsh.conf.tmp ) " +
					"|| ( mv /etc/bind/named-hsh.conf.tmp /etc/bind/named-hsh.conf && invoke-rc.d bind9 reload )")
		);		
	}

	private Processor createHiveDNSSetupProcessor(EntityManager em, Domain dom) throws ProcessorException {
		Map<String, Object> templateVars = new HashMap<String, Object>();
		templateVars.put("sio", Long.toString(System.currentTimeMillis()/1000L));
		String domName = dom.getName();
		String zonefileTargetPath = "/etc/bind/pri." + domName;
		Processor zonefileTemplateProcessor = 
			new VelocityProcessor("/de/hsadmin/mods/dom/zonefile.vm", templateVars, dom, zonefileTargetPath, false);
		Processor zonefileACLProcessor = 
			new ShellProcessor("chown root:bind " + zonefileTargetPath + " && chmod 644 " + zonefileTargetPath);
		Query query = em.createQuery("SELECT d FROM Domains d WHERE d.user.pac.hive.name = :hivename");	
		query.setParameter("hivename", dom.getUser().getHiveName());
		templateVars = new HashMap<String, Object>();
		templateVars.put("domains", query.getResultList());
		Processor prizonesFileProcessor = new CompoundProcessor(
			new VelocityProcessor("/de/hsadmin/mods/dom/named-pri-zones.vm", 
						templateVars, dom, "/etc/bind/named.pri-zones.tmp", true),
			new ShellProcessor(" ( diff -q /etc/bind/named.pri-zones.tmp /etc/bind/named.pri-zones && rm /etc/bind/named.pri-zones.tmp ) " +
					"|| ( mv /etc/bind/named.pri-zones.tmp /etc/bind/named.pri-zones && invoke-rc.d bind9 reload )")
		);		
		Processor dnsSetupProcessor = 
			new CompoundProcessor(zonefileTemplateProcessor, zonefileACLProcessor, prizonesFileProcessor);
		return dnsSetupProcessor;
	}

	private ShellProcessor createHiveDNSRemoveProcessor(String domName) {
		return new ShellProcessor("grep -v '\"pri." + domName + "\"' /etc/bind/named.pri-zones > /etc/bind/named.pri-zones.tmp" +
			" && mv /etc/bind/named.pri-zones.tmp /etc/bind/named.pri-zones" +
			" && rm /etc/bind/pri." + domName +
			" && invoke-rc.d bind9 reload");
	}

	private CompoundProcessor createHiveEMailSetupProcessor(EntityManager em, Domain dom) {
		EMailAddressProcessorFactory eMailAddressProcessorFactory = new EMailAddressProcessorFactory();
		CompoundProcessor emailAdrProcessor = new CompoundProcessor();
		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;
			emailAdrProcessor.appendProcessor(eMailAddressProcessorFactory.createCreateProcessor(em, eMailAddress));
		}
		return emailAdrProcessor;
	}

	private Processor createMailinSetupProcessor(EntityManager em, Domain dom, Pac pac) throws ProcessorException {
		String inetAddr = pac.getCurINetAddr().getInetAddr();
		CompoundProcessor cp = new CompoundProcessor(
			createPostgreyConfiguration(em),
			new ShellProcessor("postmap -r -i /etc/postfix-mailin/relaydomains", 
						dom.getName() + " anything\n" +
						"." + dom.getName() + " anything\n"));
		Query query = em.createQuery("SELECT d FROM Domains d WHERE d.domainoptions.name = :option AND d.name = :domname");
		query.setParameter("domname", dom.getName());
		query.setParameter("option", "backupmxforexternalmx");
		if (query.getResultList().isEmpty()) {
			cp.appendProcessor(
					new ShellProcessor("postmap -r -i /etc/postfix-mailin/transport", 
							dom.getName() + " smtp:" + inetAddr + ":225\n" +
							"." + dom.getName() + " smtp:" + inetAddr + ":225\n")
					);
		} else {
			cp.appendProcessor(
					new ShellProcessor(
							"postmap -d '" + dom.getName() + "' /etc/postfix-mailin/transport && " +
							"postmap -d '." + dom.getName() + "' /etc/postfix-mailin/transport")							
					);
		}
		return cp;
	}
	
	private Processor createPostgreyConfiguration(EntityManager em) throws ProcessorException {
		List<Domain> whitelistDoms = new ArrayList<Domain>();
		Query query = em.createQuery("SELECT DISTINCT dom FROM Domains dom WHERE NOT EXISTS " +
			"( SELECT postgreyDom FROM Domains postgreyDom " +
			"   WHERE postgreyDom.domainoptions.name = :option" +
			"         AND postgreyDom.name = dom.name )");
		query.setParameter("option", "greylisting");
		List<?> result = query.getResultList();
		for (Object dom : result) {
			if (dom instanceof Domain) {
				whitelistDoms.add((Domain) dom);
			}
		}
		HashMap<String, Object> templateVars = new HashMap<String, Object>();
		templateVars.put("whitelist", whitelistDoms);
		return new CompoundProcessor(
			new VelocityProcessor("/de/hsadmin/mods/dom/postgrey-whitelist-recipients.vm",
						templateVars, "/etc/postgrey/whitelist_recipients.tmp", true),
			new ShellProcessor(" ( diff -q /etc/postgrey/whitelist_recipients.tmp /etc/postgrey/whitelist_recipients && rm /etc/postgrey/whitelist_recipients.tmp ) " +
					"|| ( mv /etc/postgrey/whitelist_recipients.tmp /etc/postgrey/whitelist_recipients && invoke-rc.d postgrey reload )")
		);
	}

	private Processor createMailinUnsetupProcessor(EntityManager em, Domain dom) throws ProcessorException {
		Processor mailQueueProcessor = new CompoundProcessor(
				createPostgreyConfiguration(em),				
				new ShellProcessor(
						"postmap -d '" + dom.getName() + "' /etc/postfix-mailin/relaydomains && " +
						"postmap -d '" + dom.getName() + "' /etc/postfix-mailin/transport && " +
						"postmap -d '." + dom.getName() + "' /etc/postfix-mailin/relaydomains && " +
						"postmap -d '." + dom.getName() + "' /etc/postfix-mailin/transport"));
		return mailQueueProcessor;
	}

	private CompoundProcessor createDomainDirectoriesProcessor(Domain dom) throws ProcessorException {
		Map<String, Object> templateVars = new HashMap<String, Object>();
		UnixUser domUser = dom.getUser();
		String domName = dom.getName();
		Pac pac = domUser.getPac();
		String pacName = pac.getName();
		String homeDir = domUser.getHomedir();
		String domsDir = homeDir + "/doms";
		String userName = domUser.getName();
		String domainDir = domsDir + "/" + dom.getName();
		String[] subDirs = dom.isDynamic() ? DYNAMIC_STRUCTURE : STATIC_STRUCTURE;
		String httpdRights = "";
		if (pacName != userName) {
			httpdRights =
				"chgrp httpd " + homeDir + " && " +
				"chmod g+rx " + homeDir + " && ";
		}
		Processor mkDomainDirProzessor = 
			new ShellProcessor( httpdRights + 
					"chgrp httpd " + homeDir + " && " +
					"chmod g+rx " + homeDir + " && " +
					"mkdir --mode=1550 --parents " + domsDir + " && " +
					"chown httpd:" + pacName + " " + domsDir + " && " +
					"mkdir --mode=750 --parents " + domainDir + " && " +
					"chown " + userName + ":httpd " + domainDir 
					);
		CompoundProcessor domDirsProcessor = new CompoundProcessor(mkDomainDirProzessor);
		for (String subDir : subDirs) {
			domDirsProcessor.appendProcessor(new ShellProcessor(
					"mkdir --mode=755 --parents " + domainDir + "/" + subDir + " && " +
					"chown " + userName + ":" + pacName +  " " + domainDir + "/" + subDir
				));
		}
		templateVars.put("protocol", "http");
		domDirsProcessor.appendProcessor(
				new CreateFileProcessor("/de/hsadmin/mods/dom/htaccess.vm", templateVars, dom, domainDir + "/htdocs/.htaccess", userName, pacName, "644", false)
			);
		templateVars.put("protocol", "https");
		domDirsProcessor.appendProcessor(
				new CreateFileProcessor("/de/hsadmin/mods/dom/htaccess.vm", templateVars, dom, domainDir + "/htdocs-ssl/.htaccess", userName, pacName, "644", false)
			);
		domDirsProcessor.appendProcessor(
				new CreateFileProcessor("/de/hsadmin/mods/dom/index.html.vm", templateVars, dom, domainDir + "/subs/www/index.html", userName, pacName, "644", false)
			);
		domDirsProcessor.appendProcessor(
				new CreateFileProcessor("/de/hsadmin/mods/dom/index.html.vm", templateVars, dom, domainDir + "/subs-ssl/www/index.html", userName, pacName, "644", false)
			);
		if (dom.isDynamic()) {
			domDirsProcessor.appendProcessor(
					new CreateFileProcessor("/de/hsadmin/mods/dom/test.cgi.vm", templateVars, dom, domainDir + "/cgi/test.cgi", userName, pacName, "755", false)
				);
			domDirsProcessor.appendProcessor(
					new CreateFileProcessor("/de/hsadmin/mods/dom/test.cgi.vm", templateVars, dom, domainDir + "/cgi-ssl/test.cgi", userName, pacName, "755", false)
				);
			domDirsProcessor.appendProcessor(
					new CopyFileProcessor("/usr/local/src/phpstub/phpstub", domainDir + "/fastcgi/phpstub", userName, pacName, "755")
				);
			domDirsProcessor.appendProcessor(
					new CopyFileProcessor("/usr/local/src/phpstub/phpstub", domainDir + "/fastcgi-ssl/phpstub", userName, pacName, "755")
			);
		}
		domDirsProcessor.appendProcessor(
				new ShellProcessor("ln -sf " + domainDir + " /home/doms/ && " +
						"chown --no-dereference " + userName + ":httpd /home/doms/" + domName 
						 + " && " + "chown " + userName + ":httpd /home/doms/" + domName + "/")
			);
		return domDirsProcessor;
	}

	private Processor createApacheVHostSetupProcessor(EntityManager em, Domain dom)  throws ProcessorException {
		Map<String, Object> templateVars = new HashMap<String, Object>();
		templateVars.put("dynamicWeb", new Boolean(dom.isDynamic()));
		String domName = dom.getName();
		int level = domName.split("\\.").length;
		String linkPrefix = Integer.toString(100 - level);
		String pac = dom.getUser().getPac().getName();
		Query query = em.createQuery("SELECT d FROM Domains d WHERE d.domainoptions.name = :option AND d.name = :domname");
		query.setParameter("domname", dom.getName());
		ifOption(templateVars, query, "indexes", "+Indexes", "-Indexes");
		ifOption(templateVars, query, "includes", "+IncludesNoExec", "-Includes");
		ifOption(templateVars, query, "multiviews", "+MultiViews", "-MultiViews");
		ifOption(templateVars, query, "htdocsfallback", Boolean.TRUE, Boolean.FALSE);
		Processor domSetupProcessor = new CompoundProcessor(
			new CreateFileProcessor("/de/hsadmin/mods/dom/httpd-vhost.vm", templateVars, dom, "/etc/apache2/sites-available/" + domName + ".tmp", "root", "root", "644", true),
			new ShellProcessor("ls /etc/apache2/pems/" + pac + ".pem >/dev/null 2>&1" +
					" && sed -i '/SSLCertificate.*default/d' " + "/etc/apache2/sites-available/" + domName + ".tmp" + 
					" && (ls /etc/apache2/pems/" + pac + ".chain.pem >/dev/null 2>&1 || sed -i '/SSLCertificateChain.*" + pac + "/d' " + "/etc/apache2/sites-available/" + domName + ".tmp )" + 
					" || sed -i '/SSLCertificate.*" + pac + "/d' " + "/etc/apache2/sites-available/" + domName + ".tmp"),
			new ShellProcessor(
					" (diff -q /etc/apache2/sites-available/" + domName + ".tmp /etc/apache2/sites-available/" + domName + " && rm /etc/apache2/sites-available/" + domName + ".tmp ) " +
					" || (mv /etc/apache2/sites-available/" + domName + ".tmp /etc/apache2/sites-available/" + domName + 
					" && rm -f /etc/apache2/sites-enabled/" + linkPrefix + "-" + domName + 
					" && ln -sf /etc/apache2/sites-available/" + domName + " /etc/apache2/sites-enabled/" + linkPrefix + "-" + domName + 
					" && invoke-rc.d apache2 reload >/dev/null 2>&1)")
		);
		return domSetupProcessor;
	}

	private void ifOption(Map<String, Object> templateVars, Query query, String option, Object optIsTrue, Object optIsFalse) {
		query.setParameter("option", option);
		if (query.getResultList().isEmpty()) {
			templateVars.put(option, optIsFalse);			
		} else {
			templateVars.put(option, optIsTrue);
		}
	}

	private Processor createApacheVHostDeleteProcessor(Domain dom) {
		String domName = dom.getName();
		int level = domName.split("\\.").length;
		String linkPrefix = Integer.toString(100 - level);
		Processor vhostDelProcessor = 
			new ShellProcessor("rm -f /home/doms/" + domName +
				" && rm -f /etc/apache2/sites-enabled/" + linkPrefix + "-" + domName +
				" && rm -f /etc/apache2/sites-available/" + domName +
				" && rm -rf " + dom.getUser().getHomedir() + "/doms/" + domName +
				" && invoke-rc.d apache2 reload >/dev/null 2>&1");
		return vhostDelProcessor;
	}

}
