using Microsoft.VisualStudio.TestTools.UnitTesting;
using Strata.Base.Internal.Encryptors;
using System;
using System.Configuration;
using System.Text;

namespace Strata.Base.Internal.Tests.Security
{
    [TestClass]
    public class UserSaltEncryptionMethodTests
    {
        private const string TestUsername = "testuser";
        private const string TestOrgPin = "12345";
        private const string TestPassword = "password123";
        private static readonly Guid TestUserGuid = Guid.NewGuid();
        private const string TestSalt = "testsalt";
        private const string TestKey = "TestKey123";

        [TestInitialize]
        public void Setup()
        {
            // Set up the configuration key for testing
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings.Remove("UserSaltEncryptionKey");
            config.AppSettings.Settings.Add("UserSaltEncryptionKey", TestKey);
            config.Save();
            ConfigurationManager.RefreshSection("appSettings");
        }

        [TestMethod]
        public void Encode_ReturnsNonEmptyString()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Encoded result should not be empty");
        }

        [TestMethod]
        public void Encode_SameInputProducesSameOutput()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Act
            string result1 = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);
            string result2 = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);

            // Assert
            Assert.AreEqual(result1, result2, "Same input should produce same hash");
        }

        [TestMethod]
        public void Encode_DifferentPasswordsProduceDifferentOutputs()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();
            string differentPassword = "differentpassword123";

            // Act
            string result1 = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);
            string result2 = encryptor.Encode(TestUsername, TestOrgPin, differentPassword, TestUserGuid, TestSalt);

            // Assert
            Assert.AreNotEqual(result1, result2, "Different passwords should produce different hashes");
        }

        [TestMethod]
        public void Encode_DifferentSaltsProduceDifferentOutputs()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();
            string differentSalt = "differentsalt";

            // Act
            string result1 = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);
            string result2 = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, differentSalt);

            // Assert
            Assert.AreNotEqual(result1, result2, "Different salts should produce different hashes");
        }

        [TestMethod]
        public void Encode_OutputIsBase64String()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);

            // Assert
            try
            {
                Convert.FromBase64String(result);
            }
            catch (Exception ex)
            {
                Assert.Fail($"Result should be a valid Base64 string. Error: {ex.Message}");
            }
        }

        [TestMethod]
        public void Encode_OutputHasExpectedLength()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, TestSalt);
            byte[] decodedBytes = Convert.FromBase64String(result);

            // Assert
            Assert.AreEqual(24, decodedBytes.Length, "Output should be 24 bytes (192 bits)");
        }

        [TestMethod]
        public void Encode_WithEmptyPassword_ReturnsValidHash()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, "", TestUserGuid, TestSalt);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Empty password should still produce a hash");
            Assert.AreEqual(24, Convert.FromBase64String(result).Length, "Hash should still be 24 bytes");
        }

        [TestMethod]
        public void Encode_WithEmptySalt_ReturnsValidHash()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, "");

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Empty salt should still produce a hash");
            Assert.AreEqual(24, Convert.FromBase64String(result).Length, "Hash should still be 24 bytes");
        }

        [TestMethod]
        public void Encode_WithLongPassword_HandlesCorrectly()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();
            string longPassword = new string('a', 1000000); // 1MB password

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, longPassword, TestUserGuid, TestSalt);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Long password should produce a hash");
            Assert.AreEqual(24, Convert.FromBase64String(result).Length, "Hash should still be 24 bytes");
        }

        [TestMethod]
        public void Encode_WithLongSalt_HandlesCorrectly()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();
            string longSalt = new string('a', 1000000); // 1MB salt

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, longSalt);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Long salt should produce a hash");
            Assert.AreEqual(24, Convert.FromBase64String(result).Length, "Hash should still be 24 bytes");
        }

        [TestMethod]
        public void Encode_WithSpecialCharacters_HandlesCorrectly()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();
            string specialCharsPassword = "!@#$%^&*()_+-=[]{}|;:'\",.<>?/~`";
            string specialCharsSalt = "!@#$%^&*()_+-=[]{}|;:'\",.<>?/~`";

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, specialCharsPassword, TestUserGuid, specialCharsSalt);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Special characters should produce a hash");
            Assert.AreEqual(24, Convert.FromBase64String(result).Length, "Hash should still be 24 bytes");
        }

        [TestMethod]
        public void Encode_WithUnicodeCharacters_HandlesCorrectly()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();
            string unicodePassword = "Hello 世界! Привет мир! 안녕하세요!";
            string unicodeSalt = "Salt 世界! Соль! 소금!";

            // Act
            string result = encryptor.Encode(TestUsername, TestOrgPin, unicodePassword, TestUserGuid, unicodeSalt);

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(result), "Unicode characters should produce a hash");
            Assert.AreEqual(24, Convert.FromBase64String(result).Length, "Hash should still be 24 bytes");
        }

        [TestMethod]
        public void Encode_WithNullSalt_ThrowsArgumentNullException()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Assert
            Assert.ThrowsException<ArgumentNullException>(() => 
                encryptor.Encode(TestUsername, TestOrgPin, TestPassword, TestUserGuid, null));
        }

        [TestMethod]
        public void Encode_WithNullPassword_ThrowsArgumentNullException()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Assert
            Assert.ThrowsException<ArgumentNullException>(() => 
                encryptor.Encode(TestUsername, TestOrgPin, null, TestUserGuid, TestSalt));
        }

        [TestMethod]
        public void Encode_WithNullUsername_ThrowsArgumentNullException()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Assert
            Assert.ThrowsException<ArgumentNullException>(() => 
                encryptor.Encode(null, TestOrgPin, TestPassword, TestUserGuid, TestSalt));
        }

        [TestMethod]
        public void Encode_WithNullOrgPin_ThrowsArgumentNullException()
        {
            // Arrange
            var encryptor = new UserSaltEncryptionMethod();

            // Assert
            Assert.ThrowsException<ArgumentNullException>(() => 
                encryptor.Encode(TestUsername, null, TestPassword, TestUserGuid, TestSalt));
        }
    }
}
