package http_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"testing"

	"github.com/aws/smithy-go/logging"
	"github.com/aws/smithy-go/middleware"
	smithyhttp "github.com/aws/smithy-go/transport/http"
)

type mockLogger struct {
	bytes.Buffer
}

func (m *mockLogger) Logf(_ logging.Classification, format string, v ...interface{}) {
	m.Buffer.WriteString(fmt.Sprintf(format, v...))
	m.Buffer.WriteRune('\n')
}

func TestRequestResponseLogger(t *testing.T) {
	cases := map[string]struct {
		Middleware  smithyhttp.RequestResponseLogger
		Input       *smithyhttp.Request
		InputBody   io.ReadCloser
		Output      *smithyhttp.Response
		ExpectedLog string
	}{
		"no logging": {},
		"request": {
			Middleware: smithyhttp.RequestResponseLogger{
				LogRequest: true,
			},
			Input: &smithyhttp.Request{
				Request: &http.Request{
					URL: &url.URL{
						Scheme: "https",
						Path:   "/foo",
						Host:   "example.amazonaws.com",
					},
					Header: map[string][]string{
						"Foo": {"bar"},
					},
				},
			},
			InputBody: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))),
			ExpectedLog: "Request\n" +
				"GET /foo HTTP/1.1\r\n" +
				"Host: example.amazonaws.com\r\n" +
				"User-Agent: Go-http-client/1.1\r\n" +
				"Foo: bar\r\n" +
				"Accept-Encoding: gzip\r\n" +
				"\r\n\n",
		},
		"request with body": {
			Middleware: smithyhttp.RequestResponseLogger{
				LogRequestWithBody: true,
			},
			Input: &smithyhttp.Request{
				Request: &http.Request{
					URL: &url.URL{
						Scheme: "https",
						Path:   "/foo",
						Host:   "example.amazonaws.com",
					},
					Header: map[string][]string{
						"Foo": {"bar"},
					},
					ContentLength: 16,
				},
			},
			InputBody: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))),
			ExpectedLog: "Request\n" +
				"GET /foo HTTP/1.1\r\n" +
				"Host: example.amazonaws.com\r\n" +
				"User-Agent: Go-http-client/1.1\r\n" +
				"Content-Length: 16\r\n" +
				"Foo: bar\r\n" +
				"Accept-Encoding: gzip\r\n" +
				"\r\n" +
				"this is the body\n",
		},
		"response": {
			Middleware: smithyhttp.RequestResponseLogger{
				LogResponse: true,
			},
			Output: &smithyhttp.Response{
				Response: &http.Response{
					StatusCode:    200,
					Proto:         "HTTP/1.1",
					ContentLength: 16,
					Header: map[string][]string{
						"Foo": {"Bar"},
					},
					Body: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))),
				},
			},
			ExpectedLog: "Response\n" +
				"HTTP/0.0 200 OK\r\n" +
				"Content-Length: 16\r\n" +
				"Foo: Bar\r\n" +
				"\r\n\n",
		},
		"response with body": {
			Middleware: smithyhttp.RequestResponseLogger{
				LogResponseWithBody: true,
			},
			Output: &smithyhttp.Response{
				Response: &http.Response{
					StatusCode:    200,
					Proto:         "HTTP/1.1",
					ContentLength: 16,
					Header: map[string][]string{
						"Foo": {"Bar"},
					},
					Body: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))),
				},
			},
			ExpectedLog: "Response\n" +
				"HTTP/0.0 200 OK\r\n" +
				"Content-Length: 16\r\n" +
				"Foo: Bar\r\n" +
				"\r\n" +
				"this is the body\n",
		},
	}

	for name, tt := range cases {
		t.Run(name, func(t *testing.T) {
			logger := mockLogger{}
			ctx := middleware.SetLogger(context.Background(), &logger)

			var err error
			if tt.InputBody != nil {
				tt.Input, err = tt.Input.SetStream(tt.InputBody)
				if err != nil {
					t.Errorf("expect no error, got %v", err)
				}
			}

			_, _, err = tt.Middleware.HandleDeserialize(ctx, middleware.DeserializeInput{Request: tt.Input}, middleware.DeserializeHandlerFunc(func(ctx context.Context, input middleware.DeserializeInput) (
				middleware.DeserializeOutput, middleware.Metadata, error,
			) {
				return middleware.DeserializeOutput{RawResponse: tt.Output}, middleware.Metadata{}, nil
			}))
			if err != nil {
				t.Fatal("expect error, got nil")
			}

			actual := string(logger.Bytes())
			if tt.ExpectedLog != actual {
				t.Errorf("%v != %v", tt.ExpectedLog, actual)
			}
		})
	}
}
