using System;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime;
using UnitTests.GrainInterfaces;
using UnitTests.Tester;
using Xunit;

namespace UnitTests.General
{
    public class KeyExtensionTests : HostedTestClusterEnsureDefaultStarted
    {
        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public async Task PrimaryKeyExtensionsShouldDifferentiateGrainsUsingTheSameBasePrimaryKey()
        {
            var baseKey = Guid.NewGuid();

            const string kx1 = "1";
            const string kx2 = "2";

            var grain1 = GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey, kx1, null);
            var grainId1 = await grain1.GetGrainReference();
            var activationId1 = await grain1.GetActivationId();

            var grain2 = GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey, kx2, null);
            var grainId2 = await grain2.GetGrainReference();
            var activationId2 = await grain2.GetActivationId();

            Assert.NotEqual(grainId1, grainId2); // Mismatched key extensions should differentiate an identical base primary key.
            Assert.NotEqual(activationId1, activationId2); // Mismatched key extensions should differentiate an identical base primary key.
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public async Task PrimaryKeyExtensionsShouldDifferentiateGrainsUsingDifferentBaseKeys()
        {
            var baseKey1 = Guid.NewGuid();
            var baseKey2 = Guid.NewGuid();

            const string kx = "1";

            var grain1 = GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey1, kx, null);
            var grainId1 = await grain1.GetGrainReference();
            var activationId1 = await grain1.GetActivationId();

            var grain2 = GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey2, kx, null);
            var grainId2 = await grain2.GetGrainReference();
            var activationId2 = await grain2.GetActivationId();

            Assert.NotEqual(grainId1, grainId2); // Mismatched base keys should differentiate between identical extended keys.
            Assert.NotEqual(activationId1, activationId2); // Mismatched base keys should differentiate between identical extended keys.
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public void EmptyKeyExtensionsAreDisallowed()
        {
            Xunit.Assert.Throws(typeof(ArgumentException), () =>
            {
                var baseKey = Guid.NewGuid();

                GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey, "", null);
            });
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public void WhiteSpaceKeyExtensionsAreDisallowed()
        {
            Xunit.Assert.Throws(typeof(ArgumentException), () =>
            {
                var baseKey = Guid.NewGuid();

                GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey, " \t\n\r", null);
            });
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public void NullKeyExtensionsAreDisallowed()
        {
            Xunit.Assert.Throws(typeof(ArgumentNullException), () =>
            {
                var baseKey = Guid.NewGuid();

                GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey, null, null);
            });
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public async Task PrimaryKeyExtensionsShouldPermitStringsLongerThan127BytesLong()
        {
            var baseKey = Guid.NewGuid();

            string kx1 = new string('\\', 300);

            var localGrainRef = GrainClient.GrainFactory.GetGrain<IKeyExtensionTestGrain>(baseKey, kx1, null);
            var remoteGrainRef = await localGrainRef.GetGrainReference();

            Assert.Equal(localGrainRef, remoteGrainRef); // Mismatched grain ID.
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public void GetPrimaryKeyStringOnGrainReference()
        {
            const string key = "foo";

            var grain = GrainClient.GrainFactory.GetGrain<IStringGrain>(key);
            var key2 = ((GrainReference) grain).GetPrimaryKeyString();

            Assert.Equal(key, key2); // Unexpected key was returned.
        }

        [Fact, TestCategory("Functional"), TestCategory("PrimaryKeyExtension")]
        public void GetPrimaryKeyStringOnWrongGrainReference()
        {
            var grain = GrainClient.GrainFactory.GetGrain<ISimpleGrain>(0);
            var key = ((GrainReference)grain).GetPrimaryKeyString();
            Assert.Null(key);
        }
    }
}
