using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Threading;
using System.Web.ModelBinding;
using NUnit.Framework;
using ServiceStack;
using ServiceStack.Data;
using ServiceStack.Host;
using ServiceStack.Logging;
using ServiceStack.MsgPack;
using ServiceStack.OrmLite;
using ServiceStack.Text;
using ServiceStack.Web;

namespace RazorRockstars.Console.Files
{
    /// New Proposal, keeping ServiceStack's message-based semantics:
    /// Inspired by Ivan's proposal: http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
    /// 
    /// To align with ServiceStack's message-based design, an "Action": 
    ///   - is public and only supports a single argument the typed Request DTO 
    ///   - method name matches a HTTP Method or "Any" which is used as a fallback (for all methods) if it exists
    ///   - only returns object or void
    ///
    /// Notes: 
    /// Content Negotiation built-in, i.e. by default each method/route is automatically available in every registered Content-Type (HTTP Only).
    /// New API are also available in ServiceStack's typed service clients (they're actually even more succinct :)
    /// Any Views rendered is based on Request or Returned DTO type, see: http://razor.servicestack.net/#unified-stack

    [System.ComponentModel.Description("Description for ACodeGenTest")]
    public class ACodeGenTest
    {
        [ServiceStack.DataAnnotations.Description("Description for FirstField")]
        public int FirstField { get; set; }

        public List<string> SecondFields { get; set; }
    }

    [DataContract]
    public class ACodeGenTestResponse
    {
        [DataMember]
        [ServiceStack.DataAnnotations.Description("Description for FirstResult")]
        public int FirstResult { get; set; }

        [DataMember]
        [ApiMember(Description = "Description for SecondResult")]
        public int SecondResult { get; set; }
    }

    [Route("/reqstars/search", "GET")]
    [Route("/reqstars/aged/{Age}")]
    public class SearchReqstars : IReturn<ReqstarsResponse>
    {
        public int? Age { get; set; }
    }

    [Route("/reqstars", "GET")]
    public class AllReqstars : IReturn<List<Reqstar>> { }

    [Route("/reqstars/cached/{Aged}", "GET")]
    public class CachedAllReqstars : IReturn<ReqstarsResponse>
    {
        public int Aged { get; set; }        
    }

    public class ReqstarsResponse
    {
        public int Total { get; set; }
        public int? Aged { get; set; }
        public List<Reqstar> Results { get; set; }
        public ResponseStatus ResponseStatus { get; set; }
    }

    [Route("/reqstars/reset")]
    public class ResetReqstar : IReturnVoid { }

    [Route("/reqstars/{Id}", "GET")]
    public class GetReqstar : IReturn<Reqstar>
    {
        public int Id { get; set; }
    }

    [Route("/cached/reqstars/{Id}", "GET")]
    public class GetCachedReqstar : IReturn<Reqstar>
    {
        public int Id { get; set; }
    }

    [Route("/reqstars/{Id}/delete")]
    public class DeleteReqstar : IReturnVoid
    {
        public int Id { get; set; }
    }

    [Route("/reqstars")]
    public class Reqstar : IReturn<List<Reqstar>>
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int? Age { get; set; }
        public bool Alive { get; set; }

        public Reqstar() { }
        public Reqstar(int id, string firstName, string lastName, int age)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
            Age = age;
        }
    }

    [Route("/reqstars/{Id}", "PATCH")]
    public class UpdateReqstar : IReturn<Reqstar>
    {
        public int Id { get; set; }
        public int Age { get; set; }

        public UpdateReqstar() { }
        public UpdateReqstar(int id, int age)
        {
            Id = id;
            Age = age;
        }
    }

    [Route("/modelerror")]
    [Route("/modelerror/{StatusCode}")]
    [Route("/modelerror/{StatusCode}/{Message}")]
    public class ModelError : IReturn<ModelErrorResponse>
    {
        public int? StatusCode { get; set; }
        public string Message { get; set; }
    }

    public class ModelErrorResponse
    {
        public string Result { get; set; }
        public ResponseStatus ResponseStatus { get; set; }
    }

    public class RoutelessReqstar : IReturn<RoutelessReqstar>
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class ReqstarsByNames : List<string> { }

    [Route("/richrequest")]
    public class RichRequest : IReturn<RichRequest>
    {
        public List<string> StringList { get; set; }
        public HashSet<string> StringSet { get; set; }
        public string[] StringArray { get; set; }
    }

    public class ReqstarsService : Service
    {
        public static Reqstar[] SeedData = new[] {
            new Reqstar(1, "Foo", "Bar", 20), 
            new Reqstar(2, "Something", "Else", 30), 
            new Reqstar(3, "Foo2", "Bar2", 20), 
        };

        public object Any(ACodeGenTest request)
        {
            return new ACodeGenTestResponse { FirstResult = request.FirstField };
        }

        [EnableCors]
        public void Options(Reqstar reqstar) { }

        public void Any(ResetReqstar request)
        {
            Db.DeleteAll<Reqstar>();
            Db.Insert(SeedData);
        }

        public ReqstarsResponse Get(SearchReqstars request)
        {
            if (request.Age.HasValue && request.Age <= 0)
                throw new ArgumentException("Invalid Age");

            return new ReqstarsResponse //matches ReqstarsResponse.cshtml razor view
            {
                Aged = request.Age,
                Total = Db.Scalar<int>("select count(*) from Reqstar"),
                Results = request.Age.HasValue
                    ? Db.Select<Reqstar>(q => q.Age == request.Age.Value)
                    : Db.Select<Reqstar>()
            };
        }

        public List<Reqstar> Any(AllReqstars request)
        {
            return Db.Select<Reqstar>();
        }

        public object Any(CachedAllReqstars request)
        {
            if (request.Aged <= 0)
                throw new ArgumentException("Invalid Age");

            var cacheKey = typeof(CachedAllReqstars).Name;
            return base.Request.ToOptimizedResultUsingCache(base.Cache, cacheKey, () => 
                new ReqstarsResponse {
                    Aged = request.Aged,
                    Total = Db.Scalar<int>("select count(*) from Reqstar"),
                    Results = Db.Select<Reqstar>(q => q.Age == request.Aged)
                });
        }

        [ClientCanSwapTemplates] //allow action-level filters
        public Reqstar Get(GetReqstar request)
        {
            return Db.SingleById<Reqstar>(request.Id);
        }

        public object Get(GetCachedReqstar request)
        {
            return Request.ToOptimizedResultUsingCache(Cache, request.GetType().Name, () =>
                Db.SingleById<Reqstar>(request.Id));
        }

        public object Post(Reqstar request)
        {
            if (!request.Age.HasValue)
                throw new ArgumentException("Age is required");

            Db.Insert(request.ConvertTo<Reqstar>());
            return Db.Select<Reqstar>();
        }

        public Reqstar Patch(UpdateReqstar request)
        {
            Db.Update<Reqstar>(request, x => x.Id == request.Id);
            return Db.SingleById<Reqstar>(request.Id);
        }

        public void Any(DeleteReqstar request)
        {
            Db.DeleteById<Reqstar>(request.Id);
        }

        public RoutelessReqstar Any(RoutelessReqstar request)
        {
            return request;
        }

        public List<Reqstar> Any(ReqstarsByNames request)
        {
            return Db.Select<Reqstar>(q => Sql.In(q.FirstName, request.ToArray()));
        }

        public MixedDataContractResponse Any(MixedDataContract request)
        {
            return new MixedDataContractResponse();
        }

        public InheritsResponse Any(Inherits request)
        {
            return new InheritsResponse();
        }

        public RichRequest Get(RichRequest request)
        {
            return request;
        }

        public object Any(ModelError request)
        {
            if (request.StatusCode.HasValue)
            {
                throw new HttpError(
                    request.StatusCode.Value,
                    typeof(ArgumentException).Name,
                    request.Message);
            }
            throw new ArgumentException(request.Message + " was triggered by client");
        }
    }


    [Route("/ignore/{ignore}")]
    public class IgnoreRoute1
    {
        public string Name { get; set;  }
    }
    [Route("/ignore/{ignore}/between")]
    public class IgnoreRoute2
    {
        public string Name { get; set; }
    }
    [Route("/ignore/{ignore}/and/{ignore}")]
    [Route("/ignore/{ignore}/with/{name}")]
    public class IgnoreRoute3
    {
        public string Name { get; set; }
    }
    [Route("/ignorewildcard/{ignore*}")]
    public class IgnoreWildcardRoute
    {
        public string Name { get; set; }
    }

    public class IgnoreService : Service
    {
        public object Get(IgnoreRoute1 request)
        {
            return request;
        }

        public object Get(IgnoreRoute2 request)
        {
            return request;
        }

        public object Get(IgnoreRoute3 request)
        {
            return request;
        }

        public object Get(IgnoreWildcardRoute request)
        {
            return request;
        }
    }

    [DataContract(Name = "MixName", Namespace = "http://mix.namespace.com")]
    public class MixedDataContract : IReturn<MixedDataContractResponse>
    {
        [DataMember(Name = "MixId", Order = 1, EmitDefaultValue = false, IsRequired = true)]
        public int Id { get; set; }
    }

    [DataContract(Name = "MixedDCResponse")]
    public class MixedDataContractResponse
    {
        [DataMember(Name = "MixTotal")]
        public int Total { get; set; }
        [DataMember(Order = 2)]
        public int? Aged { get; set; }
        [DataMember(EmitDefaultValue = false)]
        public List<Rockstar> Results { get; set; }
        [DataMember(IsRequired = true)]
        public string Required { get; set; }

        [DataMember]
        public MixDataType MixDataType { get; set; }
    }

    public class MixDataType
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Inherits : InheritsBase
    {
        public string Name { get; set; }
    }

    public class InheritsBase
    {
        public int Id { get; set; }
    }

    public class InheritsResponse
    {
        public string Result { get; set; }
    }

    [Route("/filterattributes")]
    public class RequestDto : IReturn<ResponseDto> { }
    public class ResponseDto
    {
        public string Foo { get; set; }
    }

    [MyResponseFilter]
    [MyRequestFilter]
    public class MyService : Service
    {
        public object Get(RequestDto request)
        {
            return new ResponseDto { Foo = "Bar" };
        }
    }

    public class MyResponseFilterAttribute : ResponseFilterAttribute
    {
        public static int Called = 0;
        public override void Execute(IRequest req, IResponse res, object responseDto)
        {
            Called++;
            var x = responseDto;
        }
    }

    public class MyRequestFilterAttribute : RequestFilterAttribute
    {
        public static int Called = 0;
        public override void Execute(IRequest req, IResponse res, object responseDto)
        {
            Called++;
            var x = responseDto;
        }
    }

    [TestFixture]
    public class ReqStarsServiceTests
    {
        private const string ListeningOn = "http://*:2337/";
        public const string Host = "http://localhost:2337";

        private const string BaseUri = Host + "/";

        private AppHost appHost;

        private Stopwatch startedAt;

        [OneTimeSetUp]
        public void TestFixtureSetUp()
        {
            LogManager.LogFactory = new ConsoleLogFactory();
            startedAt = Stopwatch.StartNew();
            appHost = new AppHost {
                EnableRazor = true, //Uncomment for faster tests!
            };
            appHost.Plugins.Add(new MsgPackFormat());
            //appHost.Plugins.Add(new NetSerializerFormat());
            //Fast
            appHost.Init();
            HostContext.Config.DebugMode = true;
            appHost.Start(ListeningOn);
        }

        private IDbConnection db;

        [SetUp]
        public void SetUp()
        {
            db = appHost.TryResolve<IDbConnectionFactory>().OpenDbConnection();
            db.DropAndCreateTable<Reqstar>();
            db.Insert(ReqstarsService.SeedData);
        }

        [TearDown]
        public void TearDown()
        {
            db.Dispose();
        }

        [OneTimeTearDown]
        public void TestFixtureTearDown()
        {
            "Time Taken {0}ms".Fmt(startedAt.ElapsedMilliseconds).Print();
            appHost.Dispose();
        }

        [Ignore("Debug Run")]
        [Test]
        public void RunFor10Mins()
        {
            Thread.Sleep(TimeSpan.FromMinutes(10));
        }

        [Ignore("Concurrent Run")]
        [Test]
        public void Concurrent_GetReqstar_JSON()
        {
            const int NoOfThreads = 100;

            var client = new JsonServiceClient(BaseUri);

            int completed = 0;
            Exception lastEx = null;
            NoOfThreads.TimesAsync(i => {
                try
                {
                    var response = client.Get(new GetReqstar { Id = 1 });
                    Assert.That(response.Id, Is.EqualTo(1));
                }
                catch (Exception ex)
                {
                    lastEx = ex;
                }
                finally
                {
                    Interlocked.Increment(ref completed);
                }
            });

            while (completed < NoOfThreads)
                Thread.Sleep(100);

            if (lastEx != null)
                throw lastEx;
        }


        [Ignore("Concurrent Run")]
        [Test]
        public void Concurrent_GetReqstar_Razor()
        {
            const int NoOfThreads = 100;

            int completed = 0;
            Exception lastEx = null;
            string lastText = null;
            NoOfThreads.TimesAsync(i => {
                try
                {
                    lastText = "{0}/reqstars/1".Fmt(Host).GetStringFromUrl();
                }
                catch (Exception ex)
                {
                    lastEx = ex;
                }
                finally
                {
                    Interlocked.Increment(ref completed);
                }
            });

            while (completed < NoOfThreads)
                Thread.Sleep(100);

            lastText.Print();

            if (lastEx != null)
                throw lastEx;
        }

        protected static IRestClient[] RestClients = 
		{
			new JsonServiceClient(BaseUri),
			new XmlServiceClient(BaseUri),
			new JsvServiceClient(BaseUri),
			new MsgPackServiceClient(BaseUri),
            //new NetSerializerServiceClient(BaseUri), 
		};

        protected static IServiceClient[] ServiceClients =
            RestClients.OfType<IServiceClient>().ToArray();



        [Test, TestCaseSource("RestClients")]
        public void Does_allow_sending_collections(IServiceClient client)
        {
            var results = client.Send<List<Reqstar>>(new ReqstarsByNames { "Foo", "Foo2" });
        }

        [Test, TestCaseSource("RestClients")]
        public void Does_execute_request_and_response_filter_attributes(IRestClient client)
        {
            MyRequestFilterAttribute.Called = MyResponseFilterAttribute.Called = 0;
            var response = client.Get(new RequestDto());
            Assert.That(response.Foo, Is.EqualTo("Bar"));
            Assert.That(MyRequestFilterAttribute.Called, Is.EqualTo(1));
            Assert.That(MyResponseFilterAttribute.Called, Is.EqualTo(1));
        }

        [Test]
        public void Can_Process_OPTIONS_request_with_Cors_ActionFilter()
        {
            var webReq = WebRequest.CreateHttp(Host + "/reqstars");
            webReq.Method = "OPTIONS";
            using (var r = webReq.GetResponse())
            {
                Assert.That(r.Headers[HttpHeaders.AllowOrigin], Is.EqualTo(CorsFeature.DefaultOrigin));
                Assert.That(r.Headers[HttpHeaders.AllowMethods], Is.EqualTo(CorsFeature.DefaultMethods));
                Assert.That(r.Headers[HttpHeaders.AllowHeaders], Is.EqualTo(CorsFeature.DefaultHeaders));

                var response = r.GetResponseStream().ReadFully();
                Assert.That(response.Length, Is.EqualTo(0));
            }
        }


        [Test, TestCaseSource("RestClients")]
        public void Can_GET_AllReqstars(IRestClient client)
        {
            var allReqstars = client.Get<List<Reqstar>>("/reqstars");
            Assert.That(allReqstars.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Can_SEND_AllReqstars_PrettyTypedApi(IServiceClient client)
        {
            var allReqstars = client.Send(new AllReqstars());
            Assert.That(allReqstars.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_AllReqstars_PrettyRestApi(IRestClient client)
        {
            var request = new AllReqstars();
            var response = client.Get(request);

            Assert.That(request.ToUrl("GET"), Is.EqualTo("/reqstars"));
            Assert.That(response.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test]
        public void Can_GET_AllReqstars_View()
        {
            var html = "{0}/reqstars".Fmt(Host).GetStringFromUrl(accept: "text/html");
            html.Print();
            Assert.That(html, Does.Contain("<!--view:AllReqstars.cshtml-->"));
            Assert.That(html, Does.Contain("<!--template:HtmlReport.cshtml-->"));
        }


        [Test, TestCaseSource("RestClients")]
        public void Can_GET_SearchReqstars(IRestClient client)
        {
            var response = client.Get<ReqstarsResponse>("/reqstars/search");
            Assert.That(response.Results.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Disallows_SEND_SearchReqstars_PrettyTypedApi(IServiceClient client)
        {
            try
            {
                var response = client.Send(new SearchReqstars());
                Assert.Fail("POST's to SearchReqstars should not be allowed");
            }
            catch (WebServiceException webEx)
            {
                Assert.That(webEx.StatusCode, Is.EqualTo(405));
                Assert.That(webEx.StatusDescription, Is.EqualTo("Method Not Allowed"));
            }
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_SearchReqstars_PrettyRestApi(IRestClient client)
        {
            var request = new SearchReqstars();
            var response = client.Get(request);

            Assert.That(request.ToUrl("GET"), Is.EqualTo("/reqstars/search"));
            Assert.That(response.Results.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("RestClients")]
        public void Invalid_GET_SearchReqstars_throws_typed_Response_PrettyRestApi(IRestClient client)
        {
            try
            {
                var response = client.Get(new SearchReqstars { Age = -1 });
                Assert.Fail("POST's to SearchReqstars should not be allowed");
            }
            catch (WebServiceException webEx)
            {
                Assert.That(webEx.StatusCode, Is.EqualTo(400));
                Assert.That(webEx.StatusDescription, Is.EqualTo("ArgumentException"));

                Assert.That(webEx.ResponseStatus.ErrorCode, Is.EqualTo("ArgumentException"));
                Assert.That(webEx.ResponseStatus.Message, Is.EqualTo("Invalid Age"));

                Assert.That(webEx.ResponseDto as ReqstarsResponse, Is.Not.Null);
            }
        }


        [Test, TestCaseSource("RestClients")]
        public void Can_GET_SearchReqstars_aged_20(IRestClient client)
        {
            var response = client.Get<ReqstarsResponse>("/reqstars/aged/20");
            Assert.That(response.Results.Count,
                Is.EqualTo(ReqstarsService.SeedData.Count(x => x.Age == 20)));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Disallows_SEND_SearchReqstars_aged_20_PrettyTypedApi(IServiceClient client)
        {
            try
            {
                var response = client.Send(new SearchReqstars { Age = 20 });
                Assert.Fail("POST's to SearchReqstars should not be allowed");
            }
            catch (WebServiceException webEx)
            {
                Assert.That(webEx.StatusCode, Is.EqualTo(405));
                Assert.That(webEx.StatusDescription, Is.EqualTo("Method Not Allowed"));
            }
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_SearchReqstars_aged_20_PrettyRestApi(IRestClient client)
        {
            var request = new SearchReqstars { Age = 20 };
            var response = client.Get(request);

            Assert.That(request.ToUrl("GET"), Is.EqualTo("/reqstars/aged/20"));
            Assert.That(response.Results.Count,
                Is.EqualTo(ReqstarsService.SeedData.Count(x => x.Age == 20)));
        }


        [Test, TestCaseSource("RestClients")]
        public void Can_GET_GetReqstar(IRestClient client)
        {
            var response = client.Get<Reqstar>("/reqstars/1");
            Assert.That(response.FirstName,
                Is.EqualTo(ReqstarsService.SeedData[0].FirstName));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_GetReqstar_PrettyRestApi(IRestClient client)
        {
            var request = new GetReqstar { Id = 1 };
            var response = client.Get(request);

            Assert.That(request.ToUrl("GET"), Is.EqualTo("/reqstars/1"));
            Assert.That(response.FirstName,
                Is.EqualTo(ReqstarsService.SeedData[0].FirstName));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Disallows_SEND_GetReqstar_PrettyTypedApi(IServiceClient client)
        {
            try
            {
                var response = client.Send(new GetReqstar { Id = 1 });
                Assert.Fail("POST's to GetReqstar should not be allowed");
            }
            catch (WebServiceException webEx)
            {
                Assert.That(webEx.StatusCode, Is.EqualTo(405));
                Assert.That(webEx.StatusDescription, Is.EqualTo("Method Not Allowed"));
            }
        }

        [Test]
        public void Can_GET_GetReqstar_View()
        {
            var html = "{0}/reqstars/1".Fmt(Host).GetStringFromUrl(accept: "text/html");
            html.Print();
            Assert.That(html, Does.Contain("<!--view:GetReqstar.cshtml-->"));
            Assert.That(html, Does.Contain("<!--template:HtmlReport.cshtml-->"));
        }


        public class EmptyResponse { }

        [Test, TestCaseSource("RestClients")]
        public void Can_DELETE_Reqstar(IRestClient client)
        {
            var response = client.Delete<EmptyResponse>("/reqstars/1/delete");

            var reqstarsLeft = db.Select<Reqstar>();

            Assert.That(reqstarsLeft.Count,
                Is.EqualTo(ReqstarsService.SeedData.Length - 1));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Can_DELETE_Reqstar_PrettyTypedApi(IServiceClient client)
        {
            client.Send(new DeleteReqstar { Id = 1 });

            var reqstarsLeft = db.Select<Reqstar>();

            Assert.That(reqstarsLeft.Count,
                Is.EqualTo(ReqstarsService.SeedData.Length - 1));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_DELETE_Reqstar_PrettyRestApi(IRestClient client)
        {
            client.Delete(new DeleteReqstar { Id = 1 });

            var reqstarsLeft = db.Select<Reqstar>();

            Assert.That(reqstarsLeft.Count,
                Is.EqualTo(ReqstarsService.SeedData.Length - 1));
        }


        [Test, TestCaseSource("RestClients")]
        public void Can_PATCH_UpdateReqstar(IRestClient client)
        {
            var response = client.Patch<Reqstar>("/reqstars/1", new UpdateReqstar(1, 18));
            Assert.That(response.Age, Is.EqualTo(18));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_PATCH_UpdateReqstar_PrettyRestApi(IRestClient client)
        {
            var response = client.Patch(new UpdateReqstar(1, 18));
            Assert.That(response.Age, Is.EqualTo(18));
        }


        [Test, TestCaseSource("RestClients")]
        public void Can_CREATE_Reqstar(IRestClient client)
        {
            var response = client.Post<List<Reqstar>>("/reqstars", new Reqstar(4, "Just", "Created", 25));

            Assert.That(response.Count,
                Is.EqualTo(ReqstarsService.SeedData.Length + 1));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Can_CREATE_Reqstar_PrettyTypedApi(IServiceClient client)
        {
            var response = client.Send(new Reqstar(4, "Just", "Created", 25));

            Assert.That(response.Count,
                Is.EqualTo(ReqstarsService.SeedData.Length + 1));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_CREATE_Reqstar_PrettyRestApi(IRestClient client)
        {
            var response = client.Post(new Reqstar(4, "Just", "Created", 25));

            Assert.That(response.Count,
                Is.EqualTo(ReqstarsService.SeedData.Length + 1));
        }

        [Test, TestCaseSource("RestClients")]
        public void Fails_to_CREATE_Empty_Reqstar_PrettyRestApi(IRestClient client)
        {
            try
            {
                var response = client.Post(new Reqstar());
                Assert.Fail("Should've thrown 400 Bad Request Error");
            }
            catch (WebServiceException webEx)
            {
                Assert.That(webEx.StatusCode, Is.EqualTo(400));
                Assert.That(webEx.StatusDescription, Is.EqualTo("ArgumentException"));

                Assert.That(webEx.ResponseStatus.ErrorCode, Is.EqualTo("ArgumentException"));
                Assert.That(webEx.ResponseStatus.Message, Is.EqualTo("Age is required"));

                Assert.That(webEx.ResponseDto as ErrorResponse, Is.Not.Null);
            }
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_ResetReqstars(IRestClient client)
        {
            db.DeleteAll<Reqstar>();

            var response = client.Get<EmptyResponse>("/reqstars/reset");

            var reqstarsLeft = db.Select<Reqstar>();

            Assert.That(reqstarsLeft.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("ServiceClients")]
        public void Can_SEND_ResetReqstars_PrettyTypedApi(IServiceClient client)
        {
            db.DeleteAll<Reqstar>();

            client.Send(new ResetReqstar());

            var reqstarsLeft = db.Select<Reqstar>();

            Assert.That(reqstarsLeft.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_ResetReqstars_PrettyRestApi(IRestClient client)
        {
            db.DeleteAll<Reqstar>();

            client.Get(new ResetReqstar());

            var reqstarsLeft = db.Select<Reqstar>();

            Assert.That(reqstarsLeft.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_RoutelessReqstar_PrettyRestApi(IRestClient client)
        {
            var request = new RoutelessReqstar {
                Id = 1,
                FirstName = "Foo",
                LastName = "Bar",
            };

            var response = client.Get(request);

            var format = ((ServiceClientBase)client).Format;
            Assert.That(request.ToUrl("GET", format), Is.EqualTo(
                "/{0}/reply/RoutelessReqstar?id=1&firstName=Foo&lastName=Bar".Fmt(format)));
            Assert.That(response.Id, Is.EqualTo(request.Id));
            Assert.That(response.FirstName, Is.EqualTo(request.FirstName));
            Assert.That(response.LastName, Is.EqualTo(request.LastName));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_POST_RoutelessReqstar_PrettyRestApi(IRestClient client)
        {
            var request = new RoutelessReqstar {
                Id = 1,
                FirstName = "Foo",
                LastName = "Bar",
            };

            var response = client.Post(request);

            var format = ((ServiceClientBase)client).Format;
            Assert.That(request.ToUrl("POST", format), Is.EqualTo(
                "/{0}/reply/RoutelessReqstar".Fmt(format)));
            Assert.That(response.Id, Is.EqualTo(request.Id));
            Assert.That(response.FirstName, Is.EqualTo(request.FirstName));
            Assert.That(response.LastName, Is.EqualTo(request.LastName));
        }

        [Test, TestCaseSource("RestClients")]
        public void Can_GET_RichRequest_PrettyRestApi(IRestClient client)
        {
            var request = new RichRequest {
                StringArray = new[] { "a", "b", "c" },
                StringList = new List<string> { "d", "e", "f" },
                StringSet = new HashSet<string> { "g", "h", "i" },
            };

            Assert.That(request.ToUrl("GET"), Is.EqualTo("/richrequest?stringList=d,e,f&stringSet=g,h,i&stringArray=a,b,c".Replace(",","%2C")));

            var response = client.Get(request);

            Assert.That(response.StringArray, Is.EquivalentTo(request.StringArray));
            Assert.That(response.StringList, Is.EquivalentTo(request.StringList));
            Assert.That(response.StringSet, Is.EquivalentTo(request.StringSet));
        }

        [Test]
        public void Does_Cache_RazorPage()
        {
            var html = "{0}/reqstars/cached/10".Fmt(Host).GetStringFromUrl();
            Assert.That(html, Does.Contain("<h1>Counter:10</h1>"));
            html = "{0}/reqstars/cached/20".Fmt(Host).GetStringFromUrl();
            Assert.That(html, Does.Contain("<h1>Counter:10</h1>"));
        }

        [Test]
        public void Does_ignore_all_types_of_routes()
        {
            var response1 = "{0}/ignore/AnyThing?Name=foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreRoute1>();
            Assert.That(response1.Name, Is.EqualTo("foo"));
            
            var response2 = "{0}/ignore/AnyThing/between?Name=foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreRoute2>();
            Assert.That(response2.Name, Is.EqualTo("foo"));

            var response3 = "{0}/ignore/AnyThing/and/everything?Name=foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreRoute3>();
            Assert.That(response3.Name, Is.EqualTo("foo"));
            response3 = "{0}/ignore/AnyThing/with/foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreRoute3>();
            Assert.That(response3.Name, Is.EqualTo("foo"));
            
            var response4 = "{0}/ignorewildcard?Name=foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreWildcardRoute>();
            Assert.That(response4.Name, Is.EqualTo("foo"));
            response4 = "{0}/ignorewildcard/a?Name=foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreWildcardRoute>();
            Assert.That(response4.Name, Is.EqualTo("foo"));
            response4 = "{0}/ignorewildcard/a/b?Name=foo".Fmt(Host).GetJsonFromUrl().FromJson<IgnoreWildcardRoute>();
            Assert.That(response4.Name, Is.EqualTo("foo"));
        }

        [Test]
        public void Does_handle_ignored_routes()
        {
            var restPath = new RestPath(typeof(IgnoreRoute3), "/ignore/{ignore}/with/{name}");
            var pathComponents = RestPath.GetPathPartsForMatching("/ignore/AnyThing/with/foo");
            var score = restPath.MatchScore("GET", pathComponents);

            Assert.That(score, Is.GreaterThan(0));

            var request = (IgnoreRoute3) restPath.CreateRequest("/ignore/AnyThing/with/foo");
            Assert.That(request.Name, Is.EqualTo("foo"));
        }

        [Test]
        public void Does_RenderPartial_and_RenderAction()
        {
            var html = "{0}/Pages/PartialExamples".Fmt(Host)
                .GetStringFromUrl();

            Assert.That(html, Does.Contain("<!--view:PartialExamples.cshtml-->"));
            Assert.That(html, Does.Contain("<!--view:GetReqstar.cshtml-->"));
            Assert.That(html, Does.Contain("<!--view:CustomReqstar.cshtml-->"));
        }

        [Test]
        public void Does_render_cached_response()
        {
            var html1 = "{0}/cached/reqstars/1".Fmt(Host)
                .GetStringFromUrl();

            Assert.That(html1, Does.Contain("<!--view:GetCachedReqstar.cshtml-->"));

            var html2 = "{0}/cached/reqstars/1".Fmt(Host)
                .GetStringFromUrl();

            Assert.That(html2, Does.Contain("<!--view:GetCachedReqstar.cshtml-->"));

            Assert.That(html1, Is.EqualTo(html2));

            html2.Print();
        }
    }

}