package splunk // import "github.com/docker/docker/daemon/logger/splunk"

import (
	"compress/gzip"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"
	"testing"
)

func (message *splunkMessage) EventAsString() (string, error) {
	if val, ok := message.Event.(string); ok {
		return val, nil
	}
	return "", fmt.Errorf("Cannot cast Event %v to string", message.Event)
}

func (message *splunkMessage) EventAsMap() (map[string]interface{}, error) {
	if val, ok := message.Event.(map[string]interface{}); ok {
		return val, nil
	}
	return nil, fmt.Errorf("Cannot cast Event %v to map", message.Event)
}

type HTTPEventCollectorMock struct {
	tcpAddr     *net.TCPAddr
	tcpListener *net.TCPListener

	mu                  sync.Mutex
	token               string
	simulateServerError bool
	blockingCtx         context.Context

	test *testing.T

	connectionVerified bool
	gzipEnabled        *bool
	messages           []*splunkMessage
	numOfRequests      int
}

func NewHTTPEventCollectorMock(t *testing.T) *HTTPEventCollectorMock {
	tcpAddr := &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 0, Zone: ""}
	tcpListener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		t.Fatal(err)
	}
	return &HTTPEventCollectorMock{
		tcpAddr:             tcpAddr,
		tcpListener:         tcpListener,
		token:               "4642492F-D8BD-47F1-A005-0C08AE4657DF",
		simulateServerError: false,
		test:                t,
		connectionVerified:  false,
	}
}

func (hec *HTTPEventCollectorMock) simulateErr(b bool) {
	hec.mu.Lock()
	hec.simulateServerError = b
	hec.mu.Unlock()
}

func (hec *HTTPEventCollectorMock) withBlock(ctx context.Context) {
	hec.mu.Lock()
	hec.blockingCtx = ctx
	hec.mu.Unlock()
}

func (hec *HTTPEventCollectorMock) URL() string {
	return "http://" + hec.tcpListener.Addr().String()
}

func (hec *HTTPEventCollectorMock) Serve() error {
	return http.Serve(hec.tcpListener, hec)
}

func (hec *HTTPEventCollectorMock) Close() error {
	return hec.tcpListener.Close()
}

func (hec *HTTPEventCollectorMock) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	var err error

	hec.numOfRequests++

	hec.mu.Lock()
	simErr := hec.simulateServerError
	ctx := hec.blockingCtx
	hec.mu.Unlock()

	if ctx != nil {
		<-hec.blockingCtx.Done()
	}

	if simErr {
		if request.Body != nil {
			defer request.Body.Close()
		}
		writer.WriteHeader(http.StatusInternalServerError)
		return
	}

	switch request.Method {
	case http.MethodOptions:
		// Verify that options method is getting called only once
		if hec.connectionVerified {
			hec.test.Errorf("Connection should not be verified more than once. Got second request with %s method.", request.Method)
		}
		hec.connectionVerified = true
		writer.WriteHeader(http.StatusOK)
	case http.MethodPost:
		// Always verify that Driver is using correct path to HEC
		if request.URL.String() != "/services/collector/event/1.0" {
			hec.test.Errorf("Unexpected path %v", request.URL)
		}
		defer request.Body.Close()

		if authorization, ok := request.Header["Authorization"]; !ok || authorization[0] != ("Splunk "+hec.token) {
			hec.test.Error("Authorization header is invalid.")
		}

		gzipEnabled := false
		if contentEncoding, ok := request.Header["Content-Encoding"]; ok && contentEncoding[0] == "gzip" {
			gzipEnabled = true
		}

		if hec.gzipEnabled == nil {
			hec.gzipEnabled = &gzipEnabled
		} else if gzipEnabled != *hec.gzipEnabled {
			// Nothing wrong with that, but we just know that Splunk Logging Driver does not do that
			hec.test.Error("Driver should not change Content Encoding.")
		}

		var gzipReader *gzip.Reader
		var reader io.Reader
		if gzipEnabled {
			gzipReader, err = gzip.NewReader(request.Body)
			if err != nil {
				hec.test.Fatal(err)
			}
			reader = gzipReader
		} else {
			reader = request.Body
		}

		// Read body
		var body []byte
		body, err = io.ReadAll(reader)
		if err != nil {
			hec.test.Fatal(err)
		}

		// Parse message
		messageStart := 0
		for i := 0; i < len(body); i++ {
			if i == len(body)-1 || (body[i] == '}' && body[i+1] == '{') {
				var message splunkMessage
				err = json.Unmarshal(body[messageStart:i+1], &message)
				if err != nil {
					hec.test.Log(string(body[messageStart : i+1]))
					hec.test.Fatal(err)
				}
				hec.messages = append(hec.messages, &message)
				messageStart = i + 1
			}
		}

		if gzipEnabled {
			gzipReader.Close()
		}

		writer.WriteHeader(http.StatusOK)
	default:
		hec.test.Errorf("Unexpected HTTP method %s", http.MethodOptions)
		writer.WriteHeader(http.StatusBadRequest)
	}
}
