// 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.Collections.Generic;
using Xunit;

namespace Microsoft.AspNetCore.Routing.DecisionTree
{
    public class DecisionTreeBuilderTest
    {
        [Fact]
        public void BuildTree_Empty()
        {
            // Arrange
            var items = new List<Item>();

            // Act
            var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());

            // Assert
            Assert.Empty(tree.Criteria);
            Assert.Empty(tree.Matches);
        }

        [Fact]
        public void BuildTree_TrivialMatch()
        {
            // Arrange
            var items = new List<Item>();

            var item = new Item();
            items.Add(item);

            // Act
            var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());

            // Assert
            Assert.Empty(tree.Criteria);
            Assert.Same(item, Assert.Single(tree.Matches));
        }

        [Fact]
        public void BuildTree_WithMultipleCriteria()
        {
            // Arrange
            var items = new List<Item>();

            var item = new Item();
            item.Criteria.Add("area", new DecisionCriterionValue(value: "Admin"));
            item.Criteria.Add("controller", new DecisionCriterionValue(value: "Users"));
            item.Criteria.Add("action", new DecisionCriterionValue(value: "AddUser"));
            items.Add(item);

            // Act
            var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());

            // Assert
            Assert.Empty(tree.Matches);

            var area = Assert.Single(tree.Criteria);
            Assert.Equal("area", area.Key);

            var admin = Assert.Single(area.Branches);
            Assert.Equal("Admin", admin.Key);
            Assert.Empty(admin.Value.Matches);

            var controller = Assert.Single(admin.Value.Criteria);
            Assert.Equal("controller", controller.Key);

            var users = Assert.Single(controller.Branches);
            Assert.Equal("Users", users.Key);
            Assert.Empty(users.Value.Matches);

            var action = Assert.Single(users.Value.Criteria);
            Assert.Equal("action", action.Key);

            var addUser = Assert.Single(action.Branches);
            Assert.Equal("AddUser", addUser.Key);
            Assert.Empty(addUser.Value.Criteria);
            Assert.Same(item, Assert.Single(addUser.Value.Matches));
        }

        [Fact]
        public void BuildTree_WithMultipleItems()
        {
            // Arrange
            var items = new List<Item>();

            var item1 = new Item();
            item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
            item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
            items.Add(item1);

            var item2 = new Item();
            item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
            item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
            items.Add(item2);

            // Act
            var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());

            // Assert
            Assert.Empty(tree.Matches);

            var action = Assert.Single(tree.Criteria);
            Assert.Equal("action", action.Key);

            var buy = action.Branches["Buy"];
            Assert.Empty(buy.Matches);

            var controller = Assert.Single(buy.Criteria);
            Assert.Equal("controller", controller.Key);

            var store = Assert.Single(controller.Branches);
            Assert.Equal("Store", store.Key);
            Assert.Empty(store.Value.Criteria);
            Assert.Same(item1, Assert.Single(store.Value.Matches));

            var checkout = action.Branches["Checkout"];
            Assert.Empty(checkout.Matches);

            controller = Assert.Single(checkout.Criteria);
            Assert.Equal("controller", controller.Key);

            store = Assert.Single(controller.Branches);
            Assert.Equal("Store", store.Key);
            Assert.Empty(store.Value.Criteria);
            Assert.Same(item2, Assert.Single(store.Value.Matches));
        }

        [Fact]
        public void BuildTree_WithInteriorMatch()
        {
            // Arrange
            var items = new List<Item>();

            var item1 = new Item();
            item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
            item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
            items.Add(item1);

            var item2 = new Item();
            item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
            item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
            items.Add(item2);

            var item3 = new Item();
            item3.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
            items.Add(item3);

            // Act
            var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());

            // Assert
            Assert.Empty(tree.Matches);

            var action = Assert.Single(tree.Criteria);
            Assert.Equal("action", action.Key);

            var buy = action.Branches["Buy"];
            Assert.Same(item3, Assert.Single(buy.Matches));
        }

        [Fact]
        public void BuildTree_WithDivergentCriteria()
        {
            // Arrange
            var items = new List<Item>();

            var item1 = new Item();
            item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
            item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
            items.Add(item1);

            var item2 = new Item();
            item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
            item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
            items.Add(item2);

            var item3 = new Item();
            item3.Criteria.Add("stub", new DecisionCriterionValue(value: "Bleh"));
            items.Add(item3);

            // Act
            var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());

            // Assert
            Assert.Empty(tree.Matches);

            var action = tree.Criteria[0];
            Assert.Equal("action", action.Key);

            var stub = tree.Criteria[1];
            Assert.Equal("stub", stub.Key);
        }

        private class Item
        {
            public Item()
            {
                Criteria = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
            }

            public Dictionary<string, DecisionCriterionValue> Criteria { get; private set; }
        }

        private class ItemClassifier : IClassifier<Item>
        {
            public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;

            public IDictionary<string, DecisionCriterionValue> GetCriteria(Item item)
            {
                return item.Criteria;
            }
        }
    }
}