package cn.citycraft.AuthMe.security;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;

import org.bukkit.Bukkit;

import cn.citycraft.AuthMe.AuthMe;
import cn.citycraft.AuthMe.cache.auth.PlayerAuth;
import cn.citycraft.AuthMe.security.crypts.BCRYPT;
import cn.citycraft.AuthMe.security.crypts.EncryptionMethod;
import cn.citycraft.AuthMe.settings.Settings;
import fr.xephi.authme.events.PasswordEncryptionEvent;

public class PasswordSecurity {

	public static HashMap<String, String> userSalt = new HashMap<>();
	private static SecureRandom rnd = new SecureRandom();

	public static boolean comparePasswordWithHash(final String password, final String hash, final String playerName) throws NoSuchAlgorithmException {
		final HashAlgorithm algo = Settings.getPasswordHash;
		EncryptionMethod method;
		try {
			if (algo != HashAlgorithm.CUSTOM) {
				method = (EncryptionMethod) algo.getclasse().newInstance();
			} else {
				method = null;
			}

			final PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
			Bukkit.getPluginManager().callEvent(event);
			method = event.getMethod();

			if (method == null) {
				throw new NoSuchAlgorithmException("Unknown hash algorithm");
			}

			if (method.comparePassword(hash, password, playerName)) {
				return true;
			}

			if (Settings.supportOldPassword) {
				if (compareWithAllEncryptionMethod(password, hash, playerName)) {
					return true;
				}
			}
		} catch (InstantiationException | IllegalAccessException e) {
			throw new NoSuchAlgorithmException("Problem with this hash algorithm");
		}
		return false;
	}

	public static String createSalt(final int length) throws NoSuchAlgorithmException {
		final byte[] msg = new byte[40];
		rnd.nextBytes(msg);
		final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
		sha1.reset();
		final byte[] digest = sha1.digest(msg);
		return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length);
	}

	public static String getHash(final HashAlgorithm alg, final String password, final String playerName) throws NoSuchAlgorithmException {
		EncryptionMethod method;
		try {
			if (alg != HashAlgorithm.CUSTOM) {
				method = (EncryptionMethod) alg.getclasse().newInstance();
			} else {
				method = null;
			}
		} catch (InstantiationException | IllegalAccessException e) {
			throw new NoSuchAlgorithmException("Problem with this hash algorithm");
		}
		String salt = "";
		switch (alg) {
		case SHA256:
			salt = createSalt(16);
			break;
		case MD5VB:
			salt = createSalt(16);
			break;
		case XAUTH:
			salt = createSalt(12);
			break;
		case MYBB:
			salt = createSalt(8);
			userSalt.put(playerName, salt);
			break;
		case IPB3:
			salt = createSalt(5);
			userSalt.put(playerName, salt);
			break;
		case PHPFUSION:
			salt = createSalt(12);
			userSalt.put(playerName, salt);
			break;
		case SALTED2MD5:
			salt = createSalt(Settings.saltLength);
			userSalt.put(playerName, salt);
			break;
		case JOOMLA:
			salt = createSalt(32);
			userSalt.put(playerName, salt);
			break;
		case BCRYPT:
			salt = BCRYPT.gensalt(Settings.bCryptLog2Rounds);
			userSalt.put(playerName, salt);
			break;
		case WBB3:
			salt = createSalt(40);
			userSalt.put(playerName, salt);
			break;
		case WBB4:
			salt = BCRYPT.gensalt(8);
			userSalt.put(playerName, salt);
			break;
		case PBKDF2DJANGO:
		case PBKDF2:
			salt = createSalt(12);
			userSalt.put(playerName, salt);
			break;
		case SMF:
			if (method != null) {
				return method.getHash(password, null, playerName);
			}
		case PHPBB:
			salt = createSalt(16);
			userSalt.put(playerName, salt);
			break;
		case BCRYPT2Y:
			salt = createSalt(16);
			userSalt.put(playerName, salt);
			break;
		case SALTEDSHA512:
			salt = createSalt(32);
			userSalt.put(playerName, salt);
			break;
		case MD5:
		case SHA1:
		case WHIRLPOOL:
		case PLAINTEXT:
		case XENFORO:
		case SHA512:
		case ROYALAUTH:
		case CRAZYCRYPT1:
		case DOUBLEMD5:
		case WORDPRESS:
		case CUSTOM:
			break;
		default:
			throw new NoSuchAlgorithmException("Unknown hash algorithm");
		}
		final PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
		Bukkit.getPluginManager().callEvent(event);
		method = event.getMethod();
		if (method == null) {
			throw new NoSuchAlgorithmException("Unknown hash algorithm");
		}
		return method.getHash(password, salt, playerName);
	}

	private static boolean compareWithAllEncryptionMethod(final String password, final String hash, final String playerName) throws NoSuchAlgorithmException {
		for (final HashAlgorithm algo : HashAlgorithm.values()) {
			if (algo != HashAlgorithm.CUSTOM) {
				try {
					final EncryptionMethod method = (EncryptionMethod) algo.getclasse().newInstance();
					if (method.comparePassword(hash, password, playerName)) {
						final PlayerAuth nAuth = AuthMe.getInstance().database.getAuth(playerName);
						if (nAuth != null) {
							nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName));
							nAuth.setSalt(userSalt.containsKey(playerName) ? userSalt.get(playerName) : "");
							AuthMe.getInstance().database.updatePassword(nAuth);
							AuthMe.getInstance().database.updateSalt(nAuth);
						}
						return true;
					}
				} catch (final Exception ignored) {
				}
			}
		}
		return false;
	}
}
