package net.sourceforge.fenixedu.applicationTier.Servico;

import jvstm.TransactionalCommand;
import net.sourceforge.fenixedu.applicationTier.IUserView;
import net.sourceforge.fenixedu.applicationTier.Servico.exceptions.FenixServiceException;
import net.sourceforge.fenixedu.applicationTier.Servico.exceptions.InvalidPasswordServiceException;
import net.sourceforge.fenixedu.applicationTier.Servico.exceptions.PasswordExpiredServiceException;
import net.sourceforge.fenixedu.applicationTier.security.PasswordEncryptor;
import net.sourceforge.fenixedu.domain.Person;
import net.sourceforge.fenixedu.persistenceTier.ExcepcaoPersistencia;
import net.sourceforge.fenixedu.util.kerberos.KerberosException;
import net.sourceforge.fenixedu.util.kerberos.Script;
import pt.ist.fenixframework.pstm.Transaction;

public class AuthenticateKerberos extends Authenticate {

    public static abstract class WorkerThread extends Thread implements TransactionalCommand {

        private final Integer personID;

        public WorkerThread(final Person person) {
            personID = person.getIdInternal();
        }

        @Override
        public void run() {
            Transaction.withTransaction(this);
        }

        @Override
        public void doIt() {
            final Person person = (Person) rootDomainObject.readPartyByOID(personID);
            doIt(person);
        }

        protected abstract void doIt(final Person person);

        protected static void runThread(final WorkerThread workerThread) {
            workerThread.start();
            try {
                workerThread.join();
            } catch (InterruptedException e) {
                throw new Error(e);
            }
        }
    }

    public static class SetPasswordThread extends WorkerThread {

        private final String password;

        public SetPasswordThread(final Person person, final String password) {
            super(person);
            this.password = password;
        }

        @Override
        public void doIt(final Person person) {
            person.setPassword(PasswordEncryptor.encryptPassword(password));
        }

        public static void setPassword(final Person person, final String password) {
            runThread(new SetPasswordThread(person, password));
        }
    }

    public static class SetIsPassKerberos extends WorkerThread {

        public SetIsPassKerberos(final Person person) {
            super(person);
        }

        @Override
        public void doIt(final Person person) {
            person.setIsPassInKerberos(true);
        }

        public static void setIsPassKerberos(final Person person) {
            runThread(new SetIsPassKerberos(person));
        }
    }

    @Override
    public IUserView run(final String username, final String password, final String requestURL, final String remoteHost)
            throws ExcepcaoAutenticacao, FenixServiceException {

        Person person = Person.readPersonByUsernameWithOpenedLogin(username);
        if (person == null) {
            throw new ExcepcaoAutenticacao("bad.authentication");
        }

        if (person.hasIstUsername()) {
            if (person.getIsPassInKerberos()) {
                try {
                    Script.verifyPass(person.getIstUsername(), password);
                    if (!PasswordEncryptor.areEquals(person.getPassword(), password)) {
                        SetPasswordThread.setPassword(person, password);
                    }
                    return getUserView(person, requestURL);
                } catch (KerberosException ke) {
                    if (ke.getReturnCode().equals(KerberosException.CHANGE_PASSWORD_EXPIRED)) {
                        throw new PasswordExpiredServiceException(ke.getMessage());
                    }
                    if (ke.getReturnCode().equals(KerberosException.WRONG_PASSWORD)) {
                        throw new ExcepcaoAutenticacao("bad.authentication", ke);
                    } else {
                        return super.run(username, password, requestURL, remoteHost);
                    }
                } catch (ExcepcaoPersistencia ep) {
                    return super.run(username, password, requestURL, remoteHost);
                }
            } else {
                final IUserView userView = super.run(username, password, requestURL, remoteHost);
                if (userView != null) {
                    try {
                        Script.createUser(person.getIstUsername(), password);
                        SetIsPassKerberos.setIsPassKerberos(person);
                    } catch (ExcepcaoPersistencia e) {

                    } catch (KerberosException ke) {
                        String returnCode = ke.getReturnCode();
                        if (returnCode.equals(KerberosException.CHANGE_PASSWORD_TOO_SHORT)
                                || returnCode.equals(KerberosException.CHANGE_PASSWORD_NOT_ENOUGH_CHARACTER_CLASSES)
                                || returnCode.equals(KerberosException.CHANGE_PASSWORD_CANNOT_REUSE)
                                || returnCode.equals(KerberosException.ADD_NOT_ENOUGH_CHARACTER_CLASSES)
                                || returnCode.equals(KerberosException.ADD_TOO_SHORT)
                                || returnCode.equals(KerberosException.CHECK_PASSWORD_LOW_QUALITY)) {
                            throw new InvalidPasswordServiceException(ke.getReturnCode(), userView);
                        }
                    }
                }
                return userView;
            }
        } else {
            throw new FenixServiceException("error.empty.istUsername");
        }
    }
}
