package lfshttp

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync/atomic"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type verboseTest struct {
	Test string
}

func TestVerboseEnabled(t *testing.T) {
	var called uint32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		atomic.AddUint32(&called, 1)
		t.Logf("srv req %s %s", r.Method, r.URL.Path)
		assert.Equal(t, "POST", r.Method)

		assert.Equal(t, "Basic ABC", r.Header.Get("Authorization"))
		body := &verboseTest{}
		err := json.NewDecoder(r.Body).Decode(body)
		assert.Nil(t, err)
		assert.Equal(t, "Verbose", body.Test)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"Status":"Ok"}`))
	}))
	defer srv.Close()

	out := &bytes.Buffer{}
	c, _ := NewClient(nil)
	c.Verbose = true
	c.VerboseOut = out

	req, err := http.NewRequest("POST", srv.URL, nil)
	req.Header.Set("Authorization", "Basic ABC")
	req.Header.Set("Content-Type", "application/json")
	require.Nil(t, err)
	require.Nil(t, MarshalToRequest(req, verboseTest{"Verbose"}))

	res, err := c.Do(req)
	require.Nil(t, err)
	io.Copy(ioutil.Discard, res.Body)
	res.Body.Close()

	assert.Equal(t, 200, res.StatusCode)
	assert.EqualValues(t, 1, called)

	s := out.String()
	t.Log(s)

	expected := []string{
		"> Host: 127.0.0.1:",
		"\n> Authorization: Basic * * * * *\n",
		"\n> Content-Type: application/json\n",
		"\n> \n" + `{"Test":"Verbose"}` + "\n\n",

		"\n< HTTP/1.1 200 OK\n",
		"\n< Content-Type: application/json\n",
		"\n< \n" + `{"Status":"Ok"}`,
	}

	for _, substr := range expected {
		if !assert.True(t, strings.Contains(s, substr)) {
			t.Logf("missing: %q", substr)
		}
	}
}

func TestVerboseWithBinaryBody(t *testing.T) {
	var called uint32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		atomic.AddUint32(&called, 1)
		t.Logf("srv req %s %s", r.Method, r.URL.Path)
		assert.Equal(t, "POST", r.Method)

		assert.Equal(t, "Basic ABC", r.Header.Get("Authorization"))
		by, err := ioutil.ReadAll(r.Body)
		assert.Nil(t, err)
		assert.Equal(t, "binary-request", string(by))
		w.Header().Set("Content-Type", "application/octet-stream")
		w.Write([]byte(`binary-response`))
	}))
	defer srv.Close()

	out := &bytes.Buffer{}
	c, _ := NewClient(nil)
	c.Verbose = true
	c.VerboseOut = out

	buf := bytes.NewBufferString("binary-request")
	req, err := http.NewRequest("POST", srv.URL, buf)
	req.Header.Set("Authorization", "Basic ABC")
	req.Header.Set("Content-Type", "application/octet-stream")
	require.Nil(t, err)

	res, err := c.Do(req)
	require.Nil(t, err)
	io.Copy(ioutil.Discard, res.Body)
	res.Body.Close()

	assert.Equal(t, 200, res.StatusCode)
	assert.EqualValues(t, 1, called)

	s := out.String()
	t.Log(s)

	expected := []string{
		"> Host: 127.0.0.1:",
		"\n> Authorization: Basic * * * * *\n",
		"\n> Content-Type: application/octet-stream\n",

		"\n< HTTP/1.1 200 OK\n",
		"\n< Content-Type: application/octet-stream\n",
	}

	for _, substr := range expected {
		if !assert.True(t, strings.Contains(s, substr)) {
			t.Logf("missing: %q", substr)
		}
	}

	assert.False(t, strings.Contains(s, "binary"), "contains binary request or response body")
}

func TestVerboseEnabledWithDebugging(t *testing.T) {
	var called uint32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		atomic.AddUint32(&called, 1)
		t.Logf("srv req %s %s", r.Method, r.URL.Path)
		assert.Equal(t, "POST", r.Method)

		assert.Equal(t, "Basic ABC", r.Header.Get("Authorization"))
		body := &verboseTest{}
		err := json.NewDecoder(r.Body).Decode(body)
		assert.Nil(t, err)
		assert.Equal(t, "Verbose", body.Test)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"Status":"Ok"}`))
	}))
	defer srv.Close()

	out := &bytes.Buffer{}
	c, _ := NewClient(nil)
	c.Verbose = true
	c.VerboseOut = out
	c.DebuggingVerbose = true

	req, err := http.NewRequest("POST", srv.URL, nil)
	req.Header.Set("Authorization", "Basic ABC")
	req.Header.Set("Content-Type", "application/json")
	require.Nil(t, err)
	require.Nil(t, MarshalToRequest(req, verboseTest{"Verbose"}))

	res, err := c.Do(req)
	require.Nil(t, err)
	io.Copy(ioutil.Discard, res.Body)
	res.Body.Close()

	assert.Equal(t, 200, res.StatusCode)
	assert.EqualValues(t, 1, called)

	s := out.String()
	t.Log(s)

	expected := []string{
		"> Host: 127.0.0.1:",
		"\n> Authorization: Basic ABC\n",
		"\n> Content-Type: application/json\n",
		"\n> \n" + `{"Test":"Verbose"}` + "\n\n",

		"\n< HTTP/1.1 200 OK\n",
		"\n< Content-Type: application/json\n",
		"\n< \n" + `{"Status":"Ok"}`,
	}

	for _, substr := range expected {
		if !assert.True(t, strings.Contains(s, substr)) {
			t.Logf("missing: %q", substr)
		}
	}
}

func TestVerboseDisabled(t *testing.T) {
	var called uint32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		atomic.AddUint32(&called, 1)
		t.Logf("srv req %s %s", r.Method, r.URL.Path)
		assert.Equal(t, "POST", r.Method)

		assert.Equal(t, "Basic ABC", r.Header.Get("Authorization"))
		body := &verboseTest{}
		err := json.NewDecoder(r.Body).Decode(body)
		assert.Nil(t, err)
		assert.Equal(t, "Verbose", body.Test)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"Status":"Ok"}`))
	}))
	defer srv.Close()

	out := &bytes.Buffer{}
	c, _ := NewClient(nil)
	c.Verbose = false
	c.VerboseOut = out
	c.DebuggingVerbose = true

	req, err := http.NewRequest("POST", srv.URL, nil)
	req.Header.Set("Authorization", "Basic ABC")
	req.Header.Set("Content-Type", "application/json")
	require.Nil(t, err)
	require.Nil(t, MarshalToRequest(req, verboseTest{"Verbose"}))

	res, err := c.Do(req)
	require.Nil(t, err)
	io.Copy(ioutil.Discard, res.Body)
	res.Body.Close()

	assert.Equal(t, 200, res.StatusCode)
	assert.EqualValues(t, 1, called)
	assert.EqualValues(t, 0, out.Len(), out.String())
}
