package twitchserver

import (
	"fmt"
	"net/http"
	"sync"
	"testing"
	"time"

	"code.justin.tv/foundation/xray"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"goji.io/pat"
)

type ServerSuite struct {
	suite.Suite
	respond    chan struct{}
	ackRequest chan struct{}
	config     ServerConfig
}

func (s *ServerSuite) SetupTest() {
	s.respond = make(chan struct{})
	s.ackRequest = make(chan struct{})
	go s.startServer(s.T())
	waitForServerToStart()
}

var port = 8000
var mutex sync.Mutex

// assert is used to protect against internal race conditions in testify's suite
func (s *ServerSuite) startServer(t *testing.T) {
	server := NewServer()

	handler := func(w http.ResponseWriter, r *http.Request) {
		s.ackRequest <- struct{}{}

		// every request should have an ID in the context
		requestID := xray.TraceID(r.Context())
		assert.NotEmpty(t, requestID)
		// every response should have a request ID
		requestIDHeader := w.Header().Get("X-Ctxlog-LogID")
		assert.NotEmpty(t, requestIDHeader)

		s.respond <- struct{}{}
	}

	server.HandleFunc(pat.Get("/foo"), handler)

	conf := NewConfig()
	mutex.Lock()
	conf.Addr = fmt.Sprintf(":%d", port)
	port = port + 1
	s.config = *conf
	mutex.Unlock()

	err := ListenAndServe(server, conf)
	assert.NoError(t, err)
}

func waitForServerToStart() {
	time.Sleep(100 * time.Millisecond)
}

func (s *ServerSuite) TestShutdown() {
	go func() {
		<-s.ackRequest
		go Shutdown(5 * time.Second)
		<-s.respond
	}()
	_, err := http.Get(s.getServerAddr() + "foo")
	s.NoError(err, "Request should have completed successfully")
}

func (s *ServerSuite) TestShutdownTimeout() {
	go func() {
		<-s.ackRequest
		Shutdown(100 * time.Millisecond)
	}()
	_, err := http.Get(s.getServerAddr() + "foo")
	s.Error(err, "Socket should have been closed abruptly")
	<-s.respond
}

func (s *ServerSuite) TestHealthCheck() {
	resp, err := http.Get(s.getServerAddr() + "debug/running")
	s.NoError(err)
	s.Equal(http.StatusOK, resp.StatusCode)
	ShutdownNow()
}

func (s *ServerSuite) getServerAddr() string {
	mutex.Lock()
	defer mutex.Unlock()
	return fmt.Sprintf("http://localhost%s/", s.config.Addr)
}

func TestStartupAndShutdownHooks(t *testing.T) {
	server := NewServer()
	conf := NewConfig()
	postStartupHookRun := false
	preShutdownHookRun := false

	conf.RegisterPostStartupHook(func() { postStartupHookRun = true })
	conf.RegisterPreShutdownHook(func() { preShutdownHookRun = true })

	go func() {
		waitForServerToStart()
		Shutdown(5 * time.Second)
	}()

	err := ListenAndServe(server, conf)
	assert.NoError(t, err)
	assert.Nil(t, err)
	assert.Equal(t, true, postStartupHookRun)
	assert.Equal(t, true, preShutdownHookRun)
}

func TestServerSuite(t *testing.T) {
	suite.Run(t, new(ServerSuite))
}

func TestNewConfig(t *testing.T) {
	config := NewConfig()
	assert.Equal(t, ":8000", config.Addr)
	assert.Equal(t, ":6000", config.DebugAddr)
	assert.NotNil(t, config.Statter)
	assert.Equal(t, 10*time.Second, config.GracefulTimeout)
	assert.Equal(t, 5*time.Second, config.ReadHeaderTimeout)
	assert.Equal(t, 5*time.Second, config.ReadTimeout)
	assert.Equal(t, 10*time.Second, config.WriteTimeout)
	assert.Equal(t, 120*time.Second, config.IdleTimeout)
}
