package org.gcube.portlets.admin.createusers.server;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.gcube.application.framework.core.session.ASLSession;
import org.gcube.application.framework.core.session.SessionManager;
import org.gcube.common.homelibrary.home.HomeLibrary;
import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper;
import org.gcube.portlets.admin.createusers.client.HandleUsersService;
import org.gcube.portlets.admin.createusers.shared.VreUserBean;
import org.gcube.vomanagement.usermanagement.exception.GroupRetrievalFault;
import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.GCubeUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.liferay.portal.kernel.dao.jdbc.DataAccess;
import com.liferay.portal.service.UserLocalServiceUtil;

/**
 * The server side implementation of the RPC service.
 * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
 */
public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsersService{

	private final static Logger logger = LoggerFactory.getLogger(CreateUsersImpl.class);
	private static final long serialVersionUID = -3124676000683430170L;
	private static final String REGISTERED_USERS_TABLE = "registered_users";

	//dev user
	public static final String userid = "test.user";

	//dev vre
	private static final String vreID = "/gcube/devsec/devVRE";

	// SQL TABLE FIELDS
	private static final String FIELD_EMAIL = "email";
	private static final String FIELD_NAME = "name";
	private static final String FIELD_SURNAME = "surname";
	private static final String FIELD_COMPANY = "company";
	private static final String FIELD_REGISTRATION_DATE = "registration_date";
	private static final String FIELD_VRE = "vre";

	@Override
	public void init(){

		logger.debug("Trying to get connect to liferay's DB from API");
		try {

			Connection con = DataAccess.getConnection();
			logger.debug("Connected!");

			// check if the table already exists
			boolean exists = tableExists(con);

			if(exists){
				logger.debug("Table " + REGISTERED_USERS_TABLE + " already exists.");
			}
			else{
				initializeTable(con);
			}
		} catch (Exception e) {
			logger.error("Failed to connect to liferay's DB");
			return;
		}
	}

	/**
	 * check if tables exist in the database
	 * @param conn .
	 * @throws SQLException 
	 */
	private boolean tableExists(Connection con) throws SQLException {
		logger.debug("Looking for " + REGISTERED_USERS_TABLE + " table");
		Statement s = con.createStatement();
		ResultSet rs = s.executeQuery("SELECT * FROM  pg_tables where schemaname='public' and  tablename = '" + REGISTERED_USERS_TABLE +"' ");
		boolean toReturn =  rs.next();
		if (toReturn)
			logger.debug("Auxiliary Table Found! Returning ... ");
		return toReturn;
	}

	/**
	 * create the table REGISTERED_USERS_TABLE 
	 * @throws SQLException
	 */
	private void initializeTable(Connection con) {
		try {

			logger.debug("Creating table " + REGISTERED_USERS_TABLE);
			Statement s = con.createStatement();

			s.execute("CREATE TABLE " + REGISTERED_USERS_TABLE 
					+ " (id serial primary key, " // like autoincrement
					+ "email  varchar(255)  NOT NULL, "
					+ "name" + " varchar(255)  NOT NULL, "
					+ "surname" + " varchar(255)  NOT NULL, "
					+ "company" + " varchar(255)  DEFAULT NULL, "
					+ "vre" + " varchar(255)  NOT NULL, "
					+ "registration_date" + " TIMESTAMP NOT NULL)");

			logger.debug(REGISTERED_USERS_TABLE + " created");

		} catch (SQLException e) {
			logger.error("Error while creating table", e);
		}
		try {
			con.close();
		} catch (SQLException e1) {
			logger.error("Error while closing connection", e1);
		}
	}


	/**
	 * the current ASLSession
	 * @return the session
	 */
	private ASLSession getASLSession() {

		String sessionID = this.getThreadLocalRequest().getSession().getId();
		String user = (String) this.getThreadLocalRequest().getSession().getAttribute(ScopeHelper.USERNAME_ATTRIBUTE);

		if (user == null) {

			logger.warn("USER IS NULL setting " + userid + " and Running OUTSIDE PORTAL");
			user = getDevelopmentUser();
			SessionManager.getInstance().getASLSession(sessionID, user).setScope(vreID);

		}		

		return SessionManager.getInstance().getASLSession(sessionID, user);
	}

	/**
	 * Online or in development mode?
	 * @return true if you're running into the portal, false if in development
	 */
	private boolean isWithinPortal() {
		try {
			UserLocalServiceUtil.getService();
			return true;
		} 
		catch (com.liferay.portal.kernel.bean.BeanLocatorException ex) {			
			logger.trace("Development Mode ON");
			return false;
		}			
	}

	/**
	 * when packaging test will fail if the user is not set to test.user
	 * @return .
	 */
	public String getDevelopmentUser() {
		String user = userid;
		//		user = "costantino.perciante";
		return user;
	}


	@Override
	public VreUserBean register(String name, String surname, String company,
			String email, boolean sendEmail, boolean isMale) {

		// if in dev mode return ok
		if (!isWithinPortal()) {

			logger.debug("In dev mode.");
			return new VreUserBean(name, surname, company, email, false, System.currentTimeMillis(), isMale);

		}else{

			ASLSession session = getASLSession();
			String userName = session.getUsername();

			//in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info)
			//this check just return nothing if that happens
			if (userName.compareTo("test.user") == 0) {
				logger.debug("Found " + userName + " returning nothing");
				return null;
			}

			String vre = session.getScopeName();
			long timestamp = System.currentTimeMillis();

			//checking if the user has been already registered or is already in the portal
			LiferayUserManager userManager = new LiferayUserManager();
			boolean exists = userManager.userExistsByEmail(email);

			if(exists){

				logger.debug("User with this email already present in the portal.");
				return null;

			}
			else{

				logger.debug("Trying to add user: " + name + ", " + surname + ", " + email + ", " + company);
				Connection con = null;
				try{

					con = DataAccess.getConnection();
					String insert = "INSERT into " + REGISTERED_USERS_TABLE + 
							"("+ FIELD_EMAIL + ","
							+ FIELD_NAME +"," 
							+ FIELD_SURNAME + "," 
							+ FIELD_COMPANY + "," 
							+ FIELD_REGISTRATION_DATE + "," 
							+ FIELD_VRE  
							+ ") values(?, ?, ?, ?, ?, ?)";

					PreparedStatement statement = con.prepareStatement(insert);

					statement.setString(1, email);
					statement.setString(2, name);
					statement.setString(3, surname);
					statement.setString(4, company);
					statement.setTimestamp(5, new Timestamp(timestamp));
					statement.setString(6, vre);
					int res = statement.executeUpdate();

					if(res != 0){

						logger.debug("User added in " + REGISTERED_USERS_TABLE);

						// add in liferay too
						try{
							GCubeUser user = userManager.createUser(
									true,
									"", 
									email, 
									name, 
									"", 
									surname, 
									company, 
									"", 
									isMale, 
									null, 
									null,
									sendEmail, // determine if we need to send him/her an email
									true); 	// force the password reset

							// adding to the current VRE
							userManager.assignUserToGroup(getCurrentGroupID(), userManager.getUserId(user.getUsername()));

							//adding to the HL
							addUserToHLGroup(user.getUsername(), session.getScope());

						}catch(Exception e){

							// unable to create.. we need to delete it from the list of users
							logger.error("Unable to create the user " + email + " in liferay. Removing he/she from the table " + 
									REGISTERED_USERS_TABLE, e);

							deleteUserFromTable(email, con);
							return null;
						}
					}
					else{

						logger.debug("User NOT added in " + REGISTERED_USERS_TABLE);
						return null;
					}
				}catch(Exception e){
					logger.error("Unable to add user, sorry..", e);
					return null;
				}finally{
					try {
						if(con != null)
							con.close();
					} catch (SQLException e) {
						logger.error("Unable to close connection to the DB");
					}
				}

				return new VreUserBean(name, surname, company, email, false, timestamp, isMale);
			}
		}
	}

	@Override
	public List<VreUserBean> getAlreadyRegisterdUsers() {

		List<VreUserBean> toReturn = new ArrayList<VreUserBean>();

		// if in dev mode return some samples
		if (!isWithinPortal()) {

			logger.debug("In dev mode.");
			toReturn.add(new VreUserBean("Dylan", "Dog", "ISTI-CNR", "dylan.dog@gmail.com", false, System.currentTimeMillis(), true));
			toReturn.add(new VreUserBean("Costantino", "Perciante", "ISTI-CNR", "costantino8@gmail.com", false, System.currentTimeMillis(), true));
			return toReturn;

		}else{

			LiferayUserManager userManager = new LiferayUserManager();
			ASLSession session = getASLSession();
			String userName = session.getUsername();

			//in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info)
			//this check just return nothing if that happens
			if (userName.compareTo("test.user") == 0) {
				logger.debug("Found " + userName + " returning nothing");
				return null;
			}

			// evaluate current vre
			String vre = session.getScopeName();

			Connection con = null;
			try{

				con = DataAccess.getConnection();
				Statement stmt = con.createStatement();

				String sql = "SELECT * FROM " + REGISTERED_USERS_TABLE + " WHERE " + FIELD_VRE + "='" + vre + "';";
				ResultSet rs = stmt.executeQuery(sql);

				while(rs.next()){

					String name = rs.getString(FIELD_NAME);
					String surname = rs.getString(FIELD_SURNAME);
					String company = rs.getString(FIELD_COMPANY);
					String email = rs.getString(FIELD_EMAIL);
					long registrationDate = rs.getTimestamp(FIELD_REGISTRATION_DATE).getTime();

					// check if the password has been changed or not wrt the default one
					boolean passwordChanged = userManager.isPasswordChanged(email);

					// the gender information is useless (set to false)
					toReturn.add(new VreUserBean(name, surname, company, email, passwordChanged, registrationDate, false));

				}

				// now, make sure these users are still on portal
				Iterator<VreUserBean> iterator = toReturn.iterator();
				while (iterator.hasNext()) {
					VreUserBean user = (VreUserBean) iterator.next();

					if(!userManager.userExistsByEmail(user.getEmail())){

						// remove from the table
						deleteUserFromTable(user.getEmail(), con);

						// remove from this collection
						iterator.remove();

					}
				}

			}catch(Exception e){
				logger.error("Unable to retrieve users list, sorry...", e);
				return null;
			}finally{
				try {
					if(con != null)
						con.close();
				} catch (SQLException e) {
					logger.error("Unable to close connection to the DB");
				}
			}

			return toReturn;
		}
	}

	/**
	 * Remove a row from the table of the registered users
	 * @param email
	 * @param con
	 */
	private void deleteUserFromTable(String email, Connection con) {

		try{

			logger.debug("Going to delete user with email " + email);
			
			String remove = "DELETE FROM " + REGISTERED_USERS_TABLE + " WHERE " + FIELD_EMAIL + "=  ?";
			PreparedStatement statementDelete = con.prepareStatement(remove);
			statementDelete.setString(1, email);
			statementDelete.executeUpdate();

		}catch(Exception e){

			logger.error("Error while deleting user=" + email + "from the table");
		}
	}

	/**
	 * Add user to Home Library Group
	 * @param username
	 * @param group
	 */
	private void addUserToHLGroup(String username, String group) {
		try {
			org.gcube.common.homelibrary.home.workspace.usermanager.UserManager um = HomeLibrary.getHomeManagerFactory().getUserManager();
			um.associateUserToGroup(group, username);
		} catch (Exception e) {
			logger.error("Failed to get the usermanager from HL. Could not add user to the HL group");
		}
	}

	/**
	 * Get the current group ID
	 * 
	 * @return the current group ID or null if an exception is thrown
	 * @throws Exception 
	 */
	private String getCurrentGroupID() {
		ASLSession session = getASLSession();
		logger.debug("The current group NAME is --> " + session.getGroupName());	
		String toReturn = null;
		try {
			toReturn = new LiferayGroupManager().getGroupId(session.getGroupName());
		} catch (UserManagementSystemException | GroupRetrievalFault e) {
			logger.error("Unable to retrieve id for group " + session.getGroupName());
		}
		return toReturn;
	}
}
