using System.Security.Cryptography;
using AutoFixture;
using Bit.Core.Tokens;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Microsoft.AspNetCore.DataProtection;
using Xunit;

namespace Bit.Core.Test.Tokens
{
    [SutProviderCustomize]
    public class DataProtectorTokenFactoryTests
    {
        public static SutProvider<DataProtectorTokenFactory<TestTokenable>> GetSutProvider()
        {
            var fixture = new Fixture();
            return new SutProvider<DataProtectorTokenFactory<TestTokenable>>(fixture)
                .SetDependency<IDataProtectionProvider>(fixture.Create<EphemeralDataProtectionProvider>())
                .Create();
        }

        [Theory, BitAutoData]
        public void CanRoundTripTokenables(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();

            var token = sutProvider.Sut.Protect(tokenable);
            var recoveredTokenable = sutProvider.Sut.Unprotect(token);

            AssertHelper.AssertPropertyEqual(tokenable, recoveredTokenable);
        }

        [Theory, BitAutoData]
        public void PrependsClearText(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();

            var token = sutProvider.Sut.Protect(tokenable);

            Assert.StartsWith(sutProvider.GetDependency<string>("clearTextPrefix"), token);
        }

        [Theory, BitAutoData]
        public void EncryptsToken(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();
            var prefix = sutProvider.GetDependency<string>("clearTextPrefix");

            var token = sutProvider.Sut.Protect(tokenable);

            Assert.NotEqual(new Token(token).RemovePrefix(prefix), tokenable.ToToken());
        }

        [Theory, BitAutoData]
        public void ThrowsIfUnprotectFails(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();

            var token = sutProvider.Sut.Protect(tokenable);
            token += "stuff to make sure decryption fails";

            Assert.Throws<CryptographicException>(() => sutProvider.Sut.Unprotect(token));
        }

        [Theory, BitAutoData]
        public void TryUnprotect_FalseIfUnprotectFails(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();
            var token = sutProvider.Sut.Protect(tokenable) + "fail decryption";

            var result = sutProvider.Sut.TryUnprotect(token, out var data);

            Assert.False(result);
            Assert.Null(data);
        }

        [Theory, BitAutoData]
        public void TokenValid_FalseIfUnprotectFails(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();
            var token = sutProvider.Sut.Protect(tokenable) + "fail decryption";

            var result = sutProvider.Sut.TokenValid(token);

            Assert.False(result);
        }


        [Theory, BitAutoData]
        public void TokenValid_FalseIfTokenInvalid(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();

            tokenable.ForceInvalid = true;
            var token = sutProvider.Sut.Protect(tokenable);

            var result = sutProvider.Sut.TokenValid(token);

            Assert.False(result);
        }

        [Theory, BitAutoData]
        public void TryUnprotect_TrueIfSuccess(TestTokenable tokenable)
        {
            var sutProvider = GetSutProvider();
            var token = sutProvider.Sut.Protect(tokenable);

            var result = sutProvider.Sut.TryUnprotect(token, out var data);

            Assert.True(result);
            AssertHelper.AssertPropertyEqual(tokenable, data);
        }

        [Theory, BitAutoData]
        public void TokenValid_TrueIfSuccess(TestTokenable tokenable)
        {
            tokenable.ForceInvalid = false;
            var sutProvider = GetSutProvider();
            var token = sutProvider.Sut.Protect(tokenable);

            var result = sutProvider.Sut.TokenValid(token);

            Assert.True(result);
        }

    }
}