// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.TestObjects;
using Xunit;

namespace Microsoft.AspNetCore.Routing
{
    public class EndpointNameAddressSchemeTest
    {
        [Fact]
        public void AddressScheme_Match_ReturnsMatchingEndpoint()
        {
            // Arrange
            var endpoint1 = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { new EndpointNameMetadata("name1"), });

            var endpoint2 = EndpointFactory.CreateRouteEndpoint(
                "/b",
                metadata: new object[] { new EndpointNameMetadata("name2"), });

            var addressScheme = CreateAddressScheme(endpoint1, endpoint2);

            // Act
            var endpoints = addressScheme.FindEndpoints("name2");

            // Assert
            Assert.Collection(
                endpoints,
                e => Assert.Same(endpoint2, e));
        }

        [Fact]
        public void AddressScheme_NoMatch_ReturnsEmptyCollection()
        {
            // Arrange
            var endpoint = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });

            var addressScheme = CreateAddressScheme(endpoint);

            // Act
            var endpoints = addressScheme.FindEndpoints("name2");

            // Assert
            Assert.Empty(endpoints);
        }

        [Fact]
        public void AddressScheme_NoMatch_CaseSensitive()
        {
            // Arrange
            var endpoint = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });

            var addressScheme = CreateAddressScheme(endpoint);

            // Act
            var endpoints = addressScheme.FindEndpoints("NAME1");

            // Assert
            Assert.Empty(endpoints);
        }

        [Fact]
        public void AddressScheme_UpdatesWhenDataSourceChanges()
        {
            var endpoint1 = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { new EndpointNameMetadata("name1"), });
            var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });

            // Act 1
            var addressScheme = CreateAddressScheme(dynamicDataSource);

            // Assert 1
            var match = Assert.Single(addressScheme.Entries);
            Assert.Same(endpoint1, match.Value.Single());

            // Arrange 2
            var endpoint2 = EndpointFactory.CreateRouteEndpoint(
                "/b",
                metadata: new object[] { new EndpointNameMetadata("name2"), });

            // Act 2
            // Trigger change
            dynamicDataSource.AddEndpoint(endpoint2);

            // Assert 2
            Assert.Collection(
                addressScheme.Entries.OrderBy(kvp => kvp.Key),
                (m) =>
                {
                    Assert.Same(endpoint1, m.Value.Single());
                },
                (m) =>
                {
                    Assert.Same(endpoint2, m.Value.Single());
                });
        }

        [Fact]
        public void AddressScheme_IgnoresEndpointsWithSuppressLinkGeneration()
        {
            // Arrange
            var endpoint = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });

            // Act
            var addressScheme = CreateAddressScheme(endpoint);

            // Assert
            Assert.Empty(addressScheme.Entries);
        }

        [Fact]
        public void AddressScheme_UnsuppressedEndpoint_IsUsed()
        {
            // Arrange
            var endpoint = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), });

            // Act
            var addressScheme = CreateAddressScheme(endpoint);

            // Assert
            Assert.Same(endpoint, Assert.Single(Assert.Single(addressScheme.Entries).Value));
        }

        [Fact]
        public void AddressScheme_IgnoresEndpointsWithoutEndpointName()
        {
            // Arrange
            var endpoint = EndpointFactory.CreateRouteEndpoint(
                "/a",
                metadata: new object[] { });

            // Act
            var addressScheme = CreateAddressScheme(endpoint);

            // Assert
            Assert.Empty(addressScheme.Entries);
        }

        [Fact]
        public void AddressScheme_ThrowsExceptionForDuplicateEndpoints()
        {
            // Arrange
            var endpoints = new Endpoint[]
            {
                EndpointFactory.CreateRouteEndpoint("/a", displayName: "a", metadata: new object[] { new EndpointNameMetadata("name1"), }),
                EndpointFactory.CreateRouteEndpoint("/b", displayName: "b", metadata: new object[] { new EndpointNameMetadata("name1"), }),
                EndpointFactory.CreateRouteEndpoint("/c", displayName: "c", metadata: new object[] { new EndpointNameMetadata("name1"), }),

                //// Not a duplicate
                EndpointFactory.CreateRouteEndpoint("/d", displayName: "d", metadata: new object[] { new EndpointNameMetadata("NAME1"), }),

                EndpointFactory.CreateRouteEndpoint("/e", displayName: "e", metadata: new object[] { new EndpointNameMetadata("name2"), }),
                EndpointFactory.CreateRouteEndpoint("/f", displayName: "f", metadata: new object[] { new EndpointNameMetadata("name2"), }),
            };

            var addressScheme = CreateAddressScheme(endpoints);

            // Act
            var ex = Assert.Throws<InvalidOperationException>(() => addressScheme.FindEndpoints("any name"));

            // Assert
            Assert.Equal(String.Join(Environment.NewLine, @"The following endpoints with a duplicate endpoint name were found.",
"",
"Endpoints with endpoint name 'name1':",
"a",
"b",
"c",
"",
"Endpoints with endpoint name 'name2':",
"e",
"f",
""), ex.Message);
        }

        private EndpointNameAddressScheme CreateAddressScheme(params Endpoint[] endpoints)
        {
            return CreateAddressScheme(new DefaultEndpointDataSource(endpoints));
        }

        private EndpointNameAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources)
        {
            return new EndpointNameAddressScheme(new CompositeEndpointDataSource(dataSources));
        }

        private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata
        {
            public bool SuppressLinkGeneration => false;
        }
    }
}
