using System.Web.Mvc;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SportsStore.WebUI.Models;
using SportsStore.WebUI.HtmlHelpers;
using System;
using Ninject;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Concrete;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Controllers;

namespace SportsStore.UnitTests
{
    [TestClass]
    public class UnitTest1
    {
        //[TestMethod]
        //public void Can_Paginate_Page_Links()
        //{
        //    //Arrange - define an HTML helper
        //    HtmlHelper myHelper = null;

        //    //Arrange - create PagingInfo data
        //    PagingInfo pagingInfo = new PagingInfo
        //    {
        //        currentPage = 2,
        //        totalItems = 28,
        //        itemsPerPage = 10
        //    };

        //    //Arrange - set up the delete using a lambda expression
        //    Func<int, string> pageUrlDelegate = i => "Page" + i;

        //    //Act
        //    MvcHtmlString result = myHelper.PageLinks(pagingInfo, pageUrlDelegate);

        //    //Assert
        //    Assert.AreEqual(result.ToString(), 
        //        @"<a href=""Page1"">1</a><a class=""selected"" href=""Page2"">2</a><a href=""Page3"">3</a>");
        //}

        [TestMethod]
        public void Can_Send_Pagination_View_Model()
        {
            //Arrange - Create the mock repository

            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
                new Product { ProductID = 1, Name = "P1"},
                new Product { ProductID = 2, Name = "P2"},
                new Product { ProductID = 3, Name = "P3"},
                new Product { ProductID = 4, Name = "P4"},
                new Product { ProductID = 5, Name = "P5"}
            }.AsQueryable());

            //create a controller and make the page size 3 items
            ProductController target = new ProductController(mock.Object);
            target.pageSize = 3;

            //Action
            ProductsListViewModel result = (ProductsListViewModel)target.List(null, 2).Model;

            //Assert
            Product[] productArray = result.Products.ToArray();
            Assert.IsTrue(productArray.Length == 2);
            Assert.AreEqual(productArray[0].Name, "P4");
            Assert.AreEqual(productArray[1].Name, "P5");
        }

        [TestMethod]
        public void Can_Filter_Products()
        {
            //Arrange - create the mock repository
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
            new Product {ProductID = 1, Name = "P1", Category = "Cat1"},
            new Product {ProductID = 2, Name = "P2", Category = "Cat2"},
            new Product {ProductID = 3, Name = "P3", Category = "Cat1"},
            new Product {ProductID = 4, Name = "P4", Category = "Cat2"},
            new Product {ProductID = 5, Name = "P5", Category = "Cat3"}
            }.AsQueryable());

            //Arrange - create a controller and make the page size 3 items
            ProductController target = new ProductController(mock.Object);
            target.pageSize = 3;
            
            // Act
            Product[] result = ((ProductsListViewModel)target.List("Cat2", 1).Model)
            .Products.ToArray();
            
            // Assert
            Assert.AreEqual(result.Length, 2);
            Assert.IsTrue(result[0].Name == "P2" && result[0].Category == "Cat2");
            Assert.IsTrue(result[1].Name == "P4" && result[1].Category == "Cat2");
        }

        [TestMethod]
        public void Can_Create_Categories()
        {
            //Arrange - create the mock repository
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
            new Product {ProductID = 1, Name = "P1", Category = "Apples"},
            new Product {ProductID = 2, Name = "P2", Category = "Apples"},
            new Product {ProductID = 3, Name = "P3", Category = "Plums"},
            new Product {ProductID = 4, Name = "P4", Category = "Peaches"},
            new Product {ProductID = 5, Name = "P5", Category = "Oranges"}
            }.AsQueryable());

            //Arrange - create a controller
            NavController target = new NavController(mock.Object);

            // Act - get the set of categories
            string[] result = ((IEnumerable<string>)target.Menu().Model).ToArray();

            // Assert
            Assert.AreEqual(result.Length, 4);
            Assert.AreEqual(result[0], "Apples");
            Assert.AreEqual(result[1], "Oranges");
            Assert.AreEqual(result[2], "Peaches");
            Assert.AreEqual(result[3], "Plums");
        }

        [TestMethod]
        public void Indicate_Selected_Categories()
        {
            //Arrange - create the mock repository
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
            new Product {ProductID = 1, Name = "P1", Category = "Apples"},
            new Product {ProductID = 5, Name = "P5", Category = "Oranges"}
            }.AsQueryable());

            //Arrange - create a controller
            NavController target = new NavController(mock.Object);

            //Arrange - define a category to be selected
            string categoryToSelected = "Apples";

            // Act 
            string result = target.Menu(categoryToSelected).ViewBag.SelectedCategory;

            // Assert
            Assert.AreEqual(categoryToSelected, result);
        }

        [TestMethod]
        public void Generate_Category_Specific_Product_Count()
        {
            //Arrange - create the mock repository
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
            new Product {ProductID = 1, Name = "P1", Category = "Cat1"},
            new Product {ProductID = 2, Name = "P2", Category = "Cat2"},
            new Product {ProductID = 3, Name = "P3", Category = "Cat1"},
            new Product {ProductID = 4, Name = "P4", Category = "Cat2"},
            new Product {ProductID = 5, Name = "P5", Category = "Cat3"}
            }.AsQueryable());

            //Arrange - create a controller and make the page size 3 items
            ProductController target = new ProductController(mock.Object);
            target.pageSize = 3;

            // Act - test the product counts for different categories
            int res1 = ((ProductsListViewModel)target.List("Cat1").Model).PagingInfo.totalItems;
            int res2 = ((ProductsListViewModel)target.List("Cat2").Model).PagingInfo.totalItems;
            int res3 = ((ProductsListViewModel)target.List("Cat3").Model).PagingInfo.totalItems;
            int res4 = ((ProductsListViewModel)target.List("Cat4").Model).PagingInfo.totalItems;
            int resAll = ((ProductsListViewModel)target.List(null).Model).PagingInfo.totalItems;

            // Assert
            Assert.AreEqual(res1, 2);
            Assert.AreEqual(res2, 2);
            Assert.AreEqual(res3, 1);
            Assert.AreEqual(res4, 0);
            Assert.AreEqual(resAll, 5);
        }

        [TestMethod]
        public void Can_Add_New_Lines() { 
            //Arrange - Create test products
            Product prod1 = new Product { ProductID = 1, Name = "P1"};
            Product prod2 = new Product { ProductID = 2, Name = "P2"};

            //Arrange -- create a new cart
            Cart target = new Cart();

            //Act
            target.AddItem(prod1, 4);
            target.AddItem(prod2, 8);
            CartLine[] results = target.Lines.ToArray();

            //Assert
            Assert.AreEqual(results.Length, 2);
            Assert.AreEqual(results[0].Product, prod1);
            Assert.AreEqual(results[1].Product, prod2);
        }

        [TestMethod]
        public void Can_Add_Quantity_For_Existing_Lines()
        {
            //Arrange - Create test products
            Product prod1 = new Product { ProductID = 1, Name = "P1" };
            Product prod2 = new Product { ProductID = 2, Name = "P2" };

            //Arrange -- create a new cart
            Cart target = new Cart();

            //Act
            target.AddItem(prod1, 1);
            target.AddItem(prod2, 6);
            target.AddItem(prod1, 10);
            CartLine[] results = target.Lines.OrderBy(c => c.Product.ProductID).ToArray();

            //Assert
            Assert.AreEqual(results.Length, 2);
            Assert.AreEqual(results[0].Quantity, 11);
            Assert.AreEqual(results[1].Quantity, 6);
        }

        [TestMethod]
        public void Can_Remove_Line() {
            //Arrange - Create test products
            Product prod1 = new Product { ProductID = 1, Name = "P1" };
            Product prod2 = new Product { ProductID = 2, Name = "P2" };
            Product prod3 = new Product { ProductID = 3, Name = "P3" };

            //Arrange - create a new cart
            Cart target = new Cart();

            //Arrange - add some products to the cart
            target.AddItem(prod1, 2);
            target.AddItem(prod2, 5);
            target.AddItem(prod3, 6);
            target.AddItem(prod1, 3);
            target.AddItem(prod2, 8);

            //Act
            target.RemoveLine(prod2);

            //Assert
            Assert.AreEqual(target.Lines.Where(c => c.Product == prod1).Count(), 1);
            Assert.AreEqual(target.Lines.Where(c => c.Product == prod2).Count(), 0);
            Assert.AreEqual(target.Lines.Count(), 2);
        }

        [TestMethod]
        public void Calculate_Cart_Total() { 
            //Arrange - create test products
            Product prod1 = new Product { ProductID = 1, Name = "P1", Price = 100M };
            Product prod2 = new Product { ProductID = 2, Name = "P2", Price = 50M };

            //Arrange - create a new cart
            Cart target = new Cart();

            //Arrange - add some items on the cart
            target.AddItem(prod1, 1);
            target.AddItem(prod2, 1);
            target.AddItem(prod1, 3);

            //Act
            decimal result = target.ComputeTotalValue();

            //Assert
            Assert.AreEqual(result, 450M);
        }

        [TestMethod]
        public void Can_Clear_Contents() {
            //Arrange - create test products
            Product prod1 = new Product { ProductID = 1, Name = "P1", Price = 100M };
            Product prod2 = new Product { ProductID = 2, Name = "P2", Price = 50M };

            //Arrange - create a new cart
            Cart target = new Cart();

            //Arrange - add some items on the cart
            target.AddItem(prod1, 1);
            target.AddItem(prod2, 1);

            //Act - reset the cart
            target.Clear();

            //Assert
            Assert.AreEqual(target.Lines.Count(), 0);
        }

        [TestMethod]
        public void Can_Add_To_Cart() { 
            //Arrange - create the mock repository
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
                new Product {ProductID = 1, Name = "P1", Category = "Apples"},
            }.AsQueryable());

            //Arrange - create a Cart
            Cart cart = new Cart();

            //Arrange - create the controller
            CartController target = new CartController(mock.Object, null);

            //Act - add a product to the cart
            target.AddToCart(cart, 1, null);

            //Assert
            Assert.AreEqual(cart.Lines.Count(), 1);
            Assert.AreEqual(cart.Lines.ToArray()[0].Product.ProductID, 1);
        }

        [TestMethod]
        public void Adding_Product_To_Cart_Goes_To_Cart_Screen() {
            //Arrange - create the mock repository
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m => m.Products).Returns(new Product[] {
                new Product {ProductID = 1, Name = "P1", Category = "Apples"},
            }.AsQueryable());

            //Arrange - create a Cart
            Cart cart = new Cart();

            //Arrange - create the controller
            CartController target = new CartController(mock.Object, null);

            //Act - add a product to the cart
            RedirectToRouteResult result = target.AddToCart(cart, 4, "myUrl");

            //Assert
            Assert.AreEqual(result.RouteValues["action"], "Index");
            Assert.AreEqual(result.RouteValues["returnUrl"], "myUrl");
        }

        [TestMethod]
        public void Can_View_Cart_Contents() { 
            //Arrange - create a Cart
            Cart cart = new Cart();

            //Arrange - creat the controller
            CartController target = new CartController(null, null);
        
            //Act - call the Index action method
            CartIndexViewModel result = (CartIndexViewModel)target.Index(cart, "myUrl").ViewData.Model;

            //Assert
            Assert.AreSame(result.Cart, cart);
            Assert.AreSame(result.ReturnUrl, "myUrl");
        }

        [TestMethod]
        public void Cannot_Checkout_Empty_Cart() { 
            //Arrange - create a mock order processor
            Mock<IOrderProcessor> mock = new Mock<IOrderProcessor>();

            //Arrange - create an object for empty cart
            Cart cart = new Cart();

            //Arrange - create an object for shippingDetails
            ShippingDetails shippingDetails = new ShippingDetails();

            //Arrange - create an object for cart controller
            CartController target = new CartController(null, mock.Object);

            //Act
            ViewResult result = target.Checkout(cart, shippingDetails);

            //Assert - check that the order hasn't been passed on to the processor
            mock.Verify(m => m.ProcessOrder(It.IsAny<Cart>(), It.IsAny<ShippingDetails>()), Times.Never());

            //Assert - check that the method is returning the default view
            Assert.AreEqual("", result.ViewName);

            //Assert - check that we are passing an invalid model the the view
            Assert.AreEqual(false, result.ViewData.ModelState.IsValid);
        }

        [TestMethod]
        public void Cannot_Checkout_Invalid_ShippingDetails()
        {
            //Arrange - create a mock order processor
            Mock<IOrderProcessor> mock = new Mock<IOrderProcessor>();

            //Arrange - create an object for one item cart
            Cart cart = new Cart();
            cart.AddItem(new Product(), 1);

            //Arrange - create an object for shippingDetails
            ShippingDetails shippingDetails = new ShippingDetails();

            //Arrange - create an object for cart controller
            CartController target = new CartController(null, mock.Object);

            //Arrange - add an error to the model
            target.ModelState.AddModelError("error", "error");

            //Act - try to check out
            ViewResult result = target.Checkout(cart, shippingDetails);

            //Assert - check that the order hasn't been passed on to the processor
            mock.Verify(m => m.ProcessOrder(It.IsAny<Cart>(), It.IsAny<ShippingDetails>()), Times.Never());

            //Assert - check that we are passing an invalid model the the view
            Assert.AreEqual(false, result.ViewData.ModelState.IsValid);
        }

        [TestMethod]
        public void Can_Checkout_And_Submit_Order() { 
            //Arrange - create a mock order processor
            Mock<IOrderProcessor> mock = new Mock<IOrderProcessor>();

            //Arrange - create an object for one item cart
            Cart cart = new Cart();
            cart.AddItem(new Product(), 2);

            //Arrange - create an instance of the controller
            CartController target = new CartController(null, mock.Object);

            //Act - try to check out
            ViewResult result = target.Checkout(cart, new ShippingDetails());

            //Assert - check that the order has been passed on to the processor
            mock.Verify(m => m.ProcessOrder(It.IsAny<Cart>(), It.IsAny<ShippingDetails>()), Times.Once());

            //Assert - check that the method is returning the completed view
            Assert.AreEqual("Completed", result.ViewName);

            //Assert - check that we are passing a valid model to the view
            Assert.AreEqual(true, result.ViewData.ModelState.IsValid);
        }
    }
}
