package web

import (
	"bytes"
	"errors"
	"os"
	"regexp"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/valyala/fasthttp"

	"tiup-log-collector/log"
	"tiup-log-collector/testutils"
)

func TestStartWebServer(t *testing.T) {
	t.Run("should start server", func(t *testing.T) {
		var wg sync.WaitGroup

		wg.Add(1)

		cfg := Config{}
		cfg.Host = "0.0.0.0"
		cfg.Port = "29998"

		go func() {
			wg.Done()
			err := StartWebServer(&cfg)
			if err != nil {
				t.Error(err)
			}
		}()

		wg.Wait()
	})

	t.Run("should failed on duplicated port", func(t *testing.T) {
		var wg sync.WaitGroup
		errCnt := 0
		wg.Add(1)

		cfg := Config{}
		cfg.Host = "0.0.0.0"
		cfg.Port = "29999"

		go func() {
			err := StartWebServer(&cfg)
			if err != nil {
				errCnt += 1
				wg.Done()
			}
		}()

		go func() {
			err := StartWebServer(&cfg)
			if err != nil {
				errCnt += 1
				wg.Done()
			}
		}()

		wg.Wait()
		if errCnt > 1 || errCnt == 0 {
			t.Errorf("should be one failed startup")
		}
	})
}

func TestErrorHandler(t *testing.T) {
	req := testutils.NewMockRequest("", "")

	buf := new(bytes.Buffer)
	err := log.InitLogger(&log.LoggersConfig{
		EventLogWriter: os.Stderr,
		AppLogWriter:   buf,
	})
	if err != nil {
		t.Fatal(err)
	}

	res := testutils.AssertMockServe(t, req, func(ctx *fasthttp.RequestCtx) {
		errorHandler(ctx, errors.New("an error"))
	})

	testutils.SetTestingLoggerBuffer(buf)
	testutils.AssertLogNextLineMatch(t, regexp.MustCompile(".+"))
	testutils.AssertResponse(t, res, 500, `{"detail":"an error"}`)

	testutils.ResetTestLogger()
}

func TestRequestHandler(t *testing.T) {
	setMixpanelProxyServer(t, "0.0.0.0")

	t.Run("should route to dashboard proxy", func(t *testing.T) {
		ctx := fasthttp.RequestCtx{}
		ctx.Request.SetRequestURI("http://foo.bar/api/v1/dashboard/report/foo")

		requestHandler(&ctx)

		assert.Equal(t, "/foo", string(ctx.Path()), "not proxied to dashboard")
	})

	t.Run("should route to 'tiup-cluster' record", func(t *testing.T) {
		testutils.InitTestLogger()

		req := testutils.NewMockRequest("http://foo.bar/api/v1/clusters/report", "{}")

		res := testutils.AssertMockServe(t, req, requestHandler)

		testutils.AssertLogNextLineMatch(t, regexp.MustCompile(`"app":"tiup-cluster"`))
		testutils.AssertResponse(t, res, 200, `{"detail":"success"}`)

		req = testutils.NewMockRequest("http://foo.bar/api/v1/clusters/report/", "{}")

		res = testutils.AssertMockServe(t, req, requestHandler)

		testutils.AssertLogNextLineMatch(t, regexp.MustCompile(`"app":"tiup-cluster"`))
		testutils.AssertResponse(t, res, 200, `{"detail":"success"}`)

		testutils.ResetTestLogger()
	})

	t.Run("should route to 'tidb' record", func(t *testing.T) {
		testutils.InitTestLogger()

		req := testutils.NewMockRequest("http://foo.bar/api/v1/tidb/report", "{}")

		res := testutils.AssertMockServe(t, req, requestHandler)

		testutils.AssertLogNextLineMatch(t, regexp.MustCompile(`"app":"tidb"`))
		testutils.AssertResponse(t, res, 200, `{"detail":"success"}`)

		req = testutils.NewMockRequest("http://foo.bar/api/v1/tidb/report/", "{}")

		res = testutils.AssertMockServe(t, req, requestHandler)

		testutils.AssertLogNextLineMatch(t, regexp.MustCompile(`"app":"tidb"`))
		testutils.AssertResponse(t, res, 200, `{"detail":"success"}`)

		testutils.ResetTestLogger()
	})

	t.Run("should route to 'ping'", func(t *testing.T) {
		req := testutils.NewMockRequest("http://foo.bar/health_check", "")

		res := testutils.AssertMockServe(t, req, requestHandler)

		testutils.AssertResponse(t, res, 200, `{"detail":"pong"}`)
	})

	t.Run("should 404", func(t *testing.T) {
		req := testutils.NewMockRequest("http://foo.bar/foo/bar", "")

		res := testutils.AssertMockServe(t, req, requestHandler)

		testutils.AssertResponse(t, res, 404, `{"detail":"404 not found"}`)
	})
}

func TestShutdownWebServer(t *testing.T) {
	t.Run("should pass if no server", func(t *testing.T) {
		ShutdownWebServer()
	})

	t.Run("should shutdown", func(t *testing.T) {
		server = &fasthttp.Server{
			NoDefaultServerHeader: true,
			Handler: func(ctx *fasthttp.RequestCtx) {
			},
			ErrorHandler: func(ctx *fasthttp.RequestCtx, err error) {
			},
			DisableKeepalive: false,
			TCPKeepalive:     true,
			//TCPKeepalivePeriod: time.Second * time.Duration(65),
			IdleTimeout: time.Second * time.Duration(300),
			ReadTimeout: time.Second * time.Duration(80),
		}
		wg := sync.WaitGroup{}
		wg.Add(1)
		var serverErr error
		go func() {
			serverErr = server.ListenAndServe("0.0.0.0:40002")
			wg.Done()
		}()

		// Wait for start
		c := fasthttp.Client{}
		err := errors.New("wait server start")
		for err != nil {
			_, _, err = c.Get(nil, "http://127.0.0.1:40002")
		}

		ShutdownWebServer()
		wg.Wait()
		if !assert.NoError(t, serverErr) {
			t.Fatal(serverErr)
		}
	})
}
