using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Cache;
using Ocelot.Cache.Middleware;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.DownstreamRouteFinder;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Responses;
using TestStack.BDDfy;
using Xunit;

namespace Ocelot.UnitTests.Cache
{
    public class OutputCacheMiddlewareTests
    {
        private readonly Mock<IOcelotCache<HttpResponseMessage>> _cacheManager;
        private readonly Mock<IRequestScopedDataRepository> _scopedRepo;
        private readonly string _url;
        private readonly TestServer _server;
        private readonly HttpClient _client;
        private HttpResponseMessage _result;
        private HttpResponseMessage _response;

        public OutputCacheMiddlewareTests()
        {
            _cacheManager = new Mock<IOcelotCache<HttpResponseMessage>>();
            _scopedRepo = new Mock<IRequestScopedDataRepository>();

            _url = "http://localhost:51879";
            var builder = new WebHostBuilder()
                .ConfigureServices(x =>
                {
                    x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
                    x.AddLogging();
                    x.AddSingleton(_cacheManager.Object);
                    x.AddSingleton(_scopedRepo.Object);
                })
                .UseUrls(_url)
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseUrls(_url)
                .Configure(app =>
                {
                    app.UseOutputCacheMiddleware();
                });

            _scopedRepo
                .Setup(sr => sr.Get<HttpRequestMessage>("DownstreamRequest"))
                .Returns(new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")));

            _server = new TestServer(builder);
            _client = _server.CreateClient();
        }

        [Fact]
        public void should_returned_cached_item_when_it_is_in_cache()
        {
            this.Given(x => x.GivenThereIsACachedResponse(new HttpResponseMessage()))
                .And(x => x.GivenTheDownstreamRouteIs())
                .And(x => x.GivenThereIsADownstreamUrl())
                .When(x => x.WhenICallTheMiddleware())
                .Then(x => x.ThenTheCacheGetIsCalledCorrectly())
                .BDDfy();
        }

        [Fact]
        public void should_continue_with_pipeline_and_cache_response()
        {
            this.Given(x => x.GivenResponseIsNotCached())
                .And(x => x.GivenTheDownstreamRouteIs())
                .And(x => x.GivenThereAreNoErrors())
                .And(x => x.GivenThereIsADownstreamUrl())
                .When(x => x.WhenICallTheMiddleware())
                .Then(x => x.ThenTheCacheAddIsCalledCorrectly())
                .BDDfy();
        }


        private void GivenTheDownstreamRouteIs()
        {
            var reRoute = new ReRouteBuilder()
                .WithIsCached(true)
                .WithCacheOptions(new CacheOptions(100))
                .WithUpstreamHttpMethod("Get")
                .Build();
                
            var downstreamRoute = new DownstreamRoute(new List<UrlPathPlaceholderNameAndValue>(), reRoute);

            _scopedRepo
                .Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
                .Returns(new OkResponse<DownstreamRoute>(downstreamRoute));
        }

        private void GivenThereAreNoErrors()
        {
            _scopedRepo
                .Setup(x => x.Get<bool>("OcelotMiddlewareError"))
                .Returns(new OkResponse<bool>(false));
        }

        private void GivenThereIsADownstreamUrl()
        {
            _scopedRepo
                .Setup(x => x.Get<string>("DownstreamUrl"))
                .Returns(new OkResponse<string>("anything"));
        }

        private void ThenTheCacheGetIsCalledCorrectly()
        {
            _cacheManager
                .Verify(x => x.Get(It.IsAny<string>()), Times.Once);
        }

        private void ThenTheCacheAddIsCalledCorrectly()
        {
            _cacheManager
                .Verify(x => x.Add(It.IsAny<string>(), It.IsAny<HttpResponseMessage>(), It.IsAny<TimeSpan>()), Times.Once);
        }

        private void GivenResponseIsNotCached()
        {
            _scopedRepo
                .Setup(x => x.Get<HttpResponseMessage>("HttpResponseMessage"))
                .Returns(new OkResponse<HttpResponseMessage>(new HttpResponseMessage()));
        }

        private void GivenThereIsACachedResponse(HttpResponseMessage response)
        {
            _response = response;
            _cacheManager
              .Setup(x => x.Get(It.IsAny<string>()))
              .Returns(_response);
        }

        private void WhenICallTheMiddleware()
        {
            _result = _client.GetAsync(_url).Result;
        }
    }
}