using System;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
using System.Security.Cryptography;
using System.IO;

namespace AgencySystem
{
    class UserLayout : DatabaseLayout
    {
        /// <summary>
        /// Generates random encryption key (salt) for password to be encrypted
        /// </summary>
        private static Random _randomEncryptionKey = new Random();
        private static string RandomEncryptionKeyGenerator(int length)
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            return new string(Enumerable.Repeat(chars, length)
              .Select(s => s[_randomEncryptionKey.Next(s.Length)]).ToArray());
        }

        /// <summary>
        /// During creating new user, password and EncryptionKey are generated here
        /// </summary>
        /// <param name="clearText"></param>
        /// <param name="EncryptionKey"></param>
        /// <returns></returns>
        public static Tuple<string, string> EncryptPassword(string clearText, string EncryptionKey)
        {
            byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
            using (Aes encryptor = Aes.Create())
            {
                Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
                encryptor.Key = pdb.GetBytes(32);
                encryptor.IV = pdb.GetBytes(16);
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(clearBytes, 0, clearBytes.Length);
                        cs.Close();
                    }
                    clearText = Convert.ToBase64String(ms.ToArray());
                }
            }
            return new Tuple<string, string>(clearText, EncryptionKey);
        }

        /// <summary>
        /// Function which decrypts password from database to check filled login credentials
        /// </summary>
        /// <param name="cipherText"></param>
        /// <param name="EncryptionKey"></param>
        /// <returns></returns>
        public static string DecryptPasswordFromDB(string cipherText, string EncryptionKey)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            using (Aes encryptor = Aes.Create())
            {
                Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
                encryptor.Key = pdb.GetBytes(32);
                encryptor.IV = pdb.GetBytes(16);
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(cipherBytes, 0, cipherBytes.Length);
                        cs.Close();
                    }
                    cipherText = Encoding.Unicode.GetString(ms.ToArray());
                }
            }
            return cipherText;
        }
        /// <summary>
        /// Function for creating new user
        /// </summary>
        /// <param name="username"></param>
        /// <param name="firstname"></param>
        /// <param name="lastname"></param>
        /// <param name="password"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public static int CreateNewUser(string username, string firstname, string lastname, string password, int status)
        {
            //Encrypting and generating password and hash (salt)
            var passwordSet = EncryptPassword(password, RandomEncryptionKeyGenerator(16));

            try
            {
                ConnectionToDatabaseOpen();
                MySqlCommand addNewUserCmd = myConnection.CreateCommand();
                addNewUserCmd.Connection = myConnection;
                addNewUserCmd.CommandText = "INSERT INTO Users (Active, Username, Firstname, Lastname, Password, PasswordSalt) VALUES (@Active, @Username, @Firstname, @Lastname, @Password, @PasswordSalt)";
                addNewUserCmd.Parameters.AddWithValue("@Active", 1);
                addNewUserCmd.Parameters.AddWithValue("@Username", username);
                addNewUserCmd.Parameters.AddWithValue("@Firstname", firstname);
                addNewUserCmd.Parameters.AddWithValue("@Lastname", lastname);
                addNewUserCmd.Parameters.AddWithValue("@Password", passwordSet.Item1);
                addNewUserCmd.Parameters.AddWithValue("@PasswordSalt", passwordSet.Item2);
                status = addNewUserCmd.ExecuteNonQuery();
                DefaultScreen.StatusLabel.Text = DateTime.Now + " - " + Properties.Resources.CountriesUpdatedSuccessfully;
            }
            catch (Exception e)
            {
                DefaultScreen.StatusLabel.Text = DateTime.Now + " - " + e.ToString();
                Default.ErrorReporting(e);
            }
            finally
            {
                ConnectionToDatabaseClose();
            }
            return status;
        }

        /// <summary>
        /// Function to check whether the username is already in database
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public static bool IsUsernameInDatabase(string username)
        {
            int count = 0;
            try
            {
                ConnectionToDatabaseOpen();
                MySqlCommand CheckUsernameInDatabase = myConnection.CreateCommand();
                CheckUsernameInDatabase.Connection = myConnection;
                CheckUsernameInDatabase.CommandText = "SELECT count(*) FROM Users WHERE username=@username";
                //CheckUsernameInDatabase.CommandText = "SELECT count(*) FROM Users";
                CheckUsernameInDatabase.Parameters.AddWithValue("@username", username);
                //count = CheckUsernameInDatabase.ExecuteScalar();
                count = int.Parse(CheckUsernameInDatabase.ExecuteScalar().ToString());
            }
            catch (Exception e)
            {
                Default.ErrorReporting(e);
            }
            finally
            {
                ConnectionToDatabaseClose();
            }
            if (count >= 1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// This function verifies username during login
        /// </summary>
        public static int VerifyLogin(string usernameInput, string passwordInput)
        {
            /* 
                All possible returned states
                    3 = Unspecified error
                    2 = User does not exist
                    1 = Password is wrong
                    0 = Successfull
            */
            bool userExists = IsUsernameInDatabase(usernameInput);
            if (userExists)
            {
                string passwordFromDB, hashFromDB;
                try
                {
                    ConnectionToDatabaseOpen();
                    MySqlCommand TakePasswordAndHashFromDatabase = myConnection.CreateCommand();
                    TakePasswordAndHashFromDatabase.Connection = myConnection;
                    TakePasswordAndHashFromDatabase.CommandText = "SELECT Password, PasswordSalt FROM Users WHERE username=@username";
                    TakePasswordAndHashFromDatabase.Parameters.AddWithValue("@username", usernameInput);

                    using (MySqlDataReader reader = TakePasswordAndHashFromDatabase.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            passwordFromDB = String.Format("{0}", reader["Password"]);
                            hashFromDB = String.Format("{0}", reader["PasswordSalt"]);
                        }
                        else
                        {
                            return 3;
                        }
                    }
                }
                catch (Exception e)
                {
                    Default.ErrorReporting(e);
                    return 3;
                }
                finally
                {
                    ConnectionToDatabaseClose();
                }

                string DecryptedPasswordFromDB = DecryptPasswordFromDB(passwordFromDB, hashFromDB);
                if (passwordInput == DecryptedPasswordFromDB)
                {
                    return 0;
                }
                else
                {
                    return 1;
                }

            }
            else
            {
                return 2;
            }
        }
    }
}
