package Main;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;

import DB.IDB;

/**
 * The user object that must be authenticated before the system can be manipulated
 */
public class User implements IDB<User> {
	/**
	 * The user ID
	 */
	private int ID;
	/**
	 * Indicates if the user has successfully logged in
	 */
	private boolean isAuthenticated = false;
	/**
	 * Indicates if the user in an administrator
	 */
	private boolean isAdmin = false;
	/**
	 * The user's user name
	 */
	private String Username;
	/**
	 * The user'n name (optional)
	 */
	private String Name;
	/**
	 * The user's office
	 */
	private Office Office;

	public User(){
		
	}
	
	public User(String Name, String user, boolean admin, String password, Office office) {
		this.setName(Name);
		this.setUsername(user);
		this.isAdmin = admin;
		this.setOffice(office);
		try {
			if(this.save() && this.setPassword(password))
				this.isAuthenticated = true;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public User(int ID, String name, String user, boolean isAdmin, Office office){
		this.ID = ID;
		this.setName(name);
		this.setUsername(user);
		this.setAdmin(isAdmin);
		this.setOffice(office);
	}

	public User(int ID) throws SQLException {
		this.ID = ID;
		this.get();
	}

	public int getID() {
		return ID;
	}

	public boolean IsAuthenticated() {
		return this.isAuthenticated;
	}
	
	public void setAdmin(boolean isAdmin) {
		this.isAdmin = isAdmin;
	}

	public boolean IsAdmin() {
		return this.isAdmin;
	}

	public String getUsername() {
		return this.Username;
	}

	public void setUsername(String Username) {
		this.Username = Username;
	}

	public String getName() {
		return Name;
	}

	public void setName(String name) {
		Name = name;
	}

	/**
	 * Verifies the user can login
	 * @param Username
	 * @param Password
	 */
	public User login(String Username, String Password) {
		String selectSQL = "SELECT ID, Username, PasswordHash, PasswordSalt, Name, IsAdmin, OfficeID FROM Employee WHERE Username = ?;";
		try {
			PreparedStatement stmt = con.prepareStatement(selectSQL);
			stmt.setString(1, Username);
			ResultSet res = stmt.executeQuery();
			if(res.next()){
				byte[] salt = res.getBytes("PasswordSalt");
				byte[] pass = res.getBytes("PasswordHash");
				if(this.verifyPass(pass, salt, Password.getBytes("UTF-8"))){
					this.setName(res.getString("Name"));
					this.setUsername(res.getString("Username"));
					this.setOffice(new Office(res.getInt("OfficeID")));
					this.Office.setUser(this);
					this.isAdmin = res.getBoolean("IsAdmin");
					this.isAuthenticated = true;
				}
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.getErrorCode();
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public Office getOffice() {
		return Office;
	}

	public void setOffice(Office office) {
		Office = office;
	}

	/**
	 * Logs out the user and locks the system
	 */
	public void logout() {
		this.isAuthenticated = false;
		this.setName(null);
		this.setUsername(null);
		this.setOffice(null);
		this.isAdmin = false;
	}
	
	/**
	 * Set the user password
	 */
	public boolean setPassword(String pass){
        String salt = generateSalt();
		try {
	        MessageDigest md = MessageDigest.getInstance("SHA-256");
			String text = pass;//salt + pass;

			md.update(text.getBytes("UTF-8"));
	        byte[] passHash = md.digest();
	        String updatePass = "UPDATE Employee SET PasswordHash = ?, PasswordSalt = ? WHERE ID = ?;";
	        PreparedStatement stmt = con.prepareStatement(updatePass);
	        stmt.setBytes(1, passHash);
	        stmt.setBytes(2, salt.getBytes("UTF-8"));
	        stmt.setInt(3, this.getID());
	        int result = stmt.executeUpdate();
	        return result > 0 ? true : false;
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} // Change this to "UTF-16" if needed
		catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return false;
	}

	/**
	 * Converts the class to a string representation
	 */
	public String toString(){
		return this.getName() != null ? this.getName() : this.getUsername();
	}
	
	private String generateSalt(){
	    SecureRandom random = new SecureRandom();

        return new BigInteger(130, random).toString(32);
	}
	
	private boolean verifyPass(byte[] pass2, byte[] salt, byte[] bs){
        try {
			MessageDigest md = MessageDigest.getInstance("SHA-256");
//			byte[] guess = new byte[salt.length + bs.length];
//			System.arraycopy(salt, 0, guess, 0, salt.length);
//			System.arraycopy(bs, 0, guess, salt.length, bs.length);
//			md.update(guess);
			md.update(bs);
			byte[] pass = md.digest();
			return Arrays.equals(pass2, pass);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return false;
	}

	@Override
	public User get() throws SQLException {
		String selectStatement;
		if(this.getName() != null)
			selectStatement = "SELECT ID, Username, Name, IsAdmin, OfficeID FROM Employee WHERE Name = ?;";
		else
			selectStatement = "SELECT ID, Username, Name, IsAdmin, OfficeID FROM Employee WHERE ID = ?;";
		PreparedStatement statement = con.prepareStatement(selectStatement);
		if(this.getName() != null)
			statement.setString(1, this.getName());
		else
			statement.setInt(1, this.ID);
		ResultSet result = statement.executeQuery();
		if(result.next()){
			this.ID = result.getInt("ID");
			this.setUsername(result.getString("Username"));
			this.setName(result.getString("Name"));
			this.setAdmin(result.getBoolean("IsAdmin"));
			this.Office = new Office(result.getInt("OfficeID"));
			return this;
		}
		return null;
	}

	@Override
	public boolean save() throws SQLException {
		if(this.get() == null){
			String insertStatement = "INSERT INTO Employee(Username, Name, IsAdmin, OfficeID) VALUES (?, ?, ?, ?);";
			PreparedStatement statement = con.prepareStatement(insertStatement, Statement.RETURN_GENERATED_KEYS);
			statement.setString(1, this.getUsername());
			statement.setString(2, this.getName());
			statement.setBoolean(3, this.IsAdmin());
			statement.setInt(4, this.getOffice().getID());
			statement.execute();
			ResultSet keys = statement.getGeneratedKeys();
			if(keys.next()){
				this.ID = keys.getInt(1);
			}
			return statement.getUpdateCount() > 0 ? true : false;
		}
		return false;	
	}

	@Override
	public boolean update() throws SQLException {
		String updateStatement = "UPDATE Employee SET Name = ?, IsAdmin = ?, UserName = ? WHERE ID = ?;";
		PreparedStatement statement = con.prepareStatement(updateStatement);
		statement.setString(1, this.getName());
		statement.setBoolean(2, this.IsAdmin());
		statement.setString(3, this.getUsername());
		statement.setInt(4, this.getID());
		statement.executeUpdate();
		return statement.getUpdateCount() > 0 ? true : false;
	}

	@Override
	public boolean delete() throws SQLException {
		String deleteStatement = "DELETE FROM Employee WHERE ID = ?;";
		PreparedStatement statement = con.prepareStatement(deleteStatement);
		statement.setInt(1, this.getID());
		statement.executeUpdate();
		return statement.getUpdateCount() > 0 ? true : false;	
	}
}