#include "helpers.h"
#include "staticserver.h"

#include <library/cpp/testing/unittest/registar.h>

#include <util/datetime/base.h>
#include <util/datetime/cputimer.h>
#include <util/generic/ptr.h>
#include <util/generic/string.h>
#include <util/string/cast.h>
#include <util/system/maxlen.h>

using NUnitTest::RandomString;

namespace {
    const TString TextHtml = "text/html";
    const TString TextXml  = "text/xml";
}

class TStaticServerTest: public TTestBase {
private:
    UNIT_TEST_SUITE(TStaticServerTest)
        UNIT_TEST(TestPort)
        UNIT_TEST(TestAddDoc)
        UNIT_TEST(TestQuery404)
        UNIT_TEST(TestQuery200)
        UNIT_TEST(TestScript404)
        UNIT_TEST(TestScript200)
        UNIT_TEST(TestRandom)
        UNIT_TEST(TestMslevel)
        UNIT_TEST(TestCounter)
    UNIT_TEST_SUITE_END();

private:
    // Avoid any types of exceptions during object construction
    TStaticServer Hello;
    TStaticServer Goodbye;
    TStaticServer Random;

public:
    void TestPort()
    {
        UNIT_ASSERT_C(Hello.GetPort() != Goodbye.GetPort(),
            ToString(Hello.GetPort()) + " == "
            + ToString(Goodbye.GetPort()));
    }

    void TestAddDoc()
    {
        UNIT_ASSERT(Hello.AddDoc("/?hello=yes", "Hello, world!"));
        UNIT_ASSERT(!Hello.AddDoc("/?hello=yes", "Hello, world!"));
        UNIT_ASSERT(Goodbye.AddDoc("/goodbye", "..cruel world", TextXml));
        UNIT_ASSERT(!Goodbye.AddDoc("/goodbye", "..cruel world"));
    }

    void TestQuery404()
    {
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(
            Hello.GetPort(), "/?hello=no").HttpCode, unsigned(404));
        UNIT_ASSERT_VALUES_EQUAL(
            GetHttpData(Hello.GetPort(), "/goodbye").HttpCode, unsigned(404));
    }

    void TestQuery200()
    {
        THttpResponse response = GetHttpData(Hello.GetPort(), "/?hello=yes");
        UNIT_ASSERT_VALUES_EQUAL(response.HttpCode, unsigned(200));
        UNIT_ASSERT_VALUES_EQUAL(response.Body, "Hello, world!");
        const TString& contentType =
            GetHeader(response.Headers, "Content-Type");
        UNIT_ASSERT_C(contentType == "text/plain"
            || contentType.StartsWith("text/plain;"),
            "Content-Type header has incorrect value " + contentType.Quote());
    }

    void TestScript404()
    {
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(
            Goodbye.GetPort(), "/?hello=yes").HttpCode, unsigned(404));
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(
            Goodbye.GetPort(), "/goodday").HttpCode, unsigned(404));
    }

    void TestScript200()
    {
        THttpResponse response = GetHttpData(Goodbye.GetPort(), "/goodbye");
        UNIT_ASSERT_VALUES_EQUAL(response.HttpCode, unsigned(200));
        UNIT_ASSERT_VALUES_EQUAL(response.Body, "..cruel world");
        const TString& contentType =
            GetHeader(response.Headers, "Content-Type");
        UNIT_ASSERT_C(contentType == "text/xml"
            || contentType.StartsWith("text/xml;"),
            "Content-Type header has incorrect value " + contentType.Quote());
    }

    void TestRandom()
    {
        UNIT_ASSERT(Random.AddDoc(TString("/random?what=nothing&hi").data(),
            RandomString(URL_MAX / 2), TextHtml));
        THttpResponse response = GetHttpData(Random.GetPort(),
            TString("/random?what=nothing&hi").data());
        UNIT_ASSERT_VALUES_EQUAL(response.HttpCode, unsigned(200));
        UNIT_ASSERT_VALUES_EQUAL(response.Body, RandomString(URL_MAX / 2));
        const TString& contentType =
            GetHeader(response.Headers, "Content-Type");
        UNIT_ASSERT_C(contentType == "text/html"
            || contentType.StartsWith("text/html;"),
            "Content-Type header has incorrect value " + contentType.Quote());
    }

    void TestMslevel()
    {
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(Hello.GetPort(),
            "/?hello=yes&mslevel=1").HttpCode, unsigned(200));
        UNIT_ASSERT_VALUES_EQUAL(
            GetHttpData(Hello.GetPort(), "/?hello=yes&mslevel=1").Body,
            GetHttpData(Hello.GetPort(), "/?hello=yes").Body);
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(Random.GetPort(),
            "/random?what=nothing&mslevel=2&hi").HttpCode, unsigned(200));
    }

    void TestCounter()
    {
        TStaticServer slowpoke(TDuration::MilliSeconds(50));
        UNIT_ASSERT(slowpoke.AddDoc("/slowpoke", "Lightning!"));
        TSimpleTimer slowpokeTimer;
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(slowpoke.GetPort(),
            "/slowpoke").HttpCode, unsigned(200));
        TDuration slowpokeLatency = slowpokeTimer.Get();
        UNIT_ASSERT_C(slowpokeLatency > TDuration::MilliSeconds(45),
            "Slowpoke server responded in " + ToString(slowpokeLatency));
        UNIT_ASSERT_VALUES_EQUAL(slowpoke.GetCallsCount(), 1u);
        slowpokeTimer.Reset();
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(slowpoke.GetPort(),
            "/slowpoke").HttpCode, unsigned(200));
        slowpokeLatency = slowpokeTimer.Get();
        UNIT_ASSERT_C(slowpokeLatency > TDuration::MilliSeconds(45),
            "Slowpoke server responded in " + ToString(slowpokeLatency));
        UNIT_ASSERT_VALUES_EQUAL(slowpoke.GetCallsCount(), 2u);
        slowpokeTimer.Reset();
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(slowpoke.GetPort(),
            "/slowpoke").HttpCode, unsigned(200));
        UNIT_ASSERT_VALUES_EQUAL(GetHttpData(slowpoke.GetPort(),
            "/slowpoke").HttpCode, unsigned(200));
        slowpokeLatency = slowpokeTimer.Get();
        UNIT_ASSERT_C(slowpokeLatency > TDuration::MilliSeconds(90),
            "Slowpoke server responded in " + ToString(slowpokeLatency));
        UNIT_ASSERT_VALUES_EQUAL(slowpoke.GetCallsCount(), 4u);
    }
};

UNIT_TEST_SUITE_REGISTRATION(TStaticServerTest)

