package httpserver

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

	"github.com/qdm12/gosettings/validate"
	"github.com/stretchr/testify/assert"
)

func Test_Settings_SetDefaults(t *testing.T) {
	t.Parallel()

	const defaultTimeout = 3 * time.Second

	testCases := map[string]struct {
		settings Settings
		expected Settings
	}{
		"empty settings": {
			settings: Settings{},
			expected: Settings{
				Address:           ":8000",
				ReadHeaderTimeout: defaultTimeout,
				ReadTimeout:       defaultTimeout,
				ShutdownTimeout:   defaultTimeout,
			},
		},
		"filled settings": {
			settings: Settings{
				Address:           ":8001",
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
			expected: Settings{
				Address:           ":8001",
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			testCase.settings.SetDefaults()

			assert.Equal(t, testCase.expected, testCase.settings)
		})
	}
}

func Test_Settings_Copy(t *testing.T) {
	t.Parallel()

	someHandler := http.NewServeMux()
	someLogger := &testLogger{}

	testCases := map[string]struct {
		settings Settings
		expected Settings
	}{
		"empty settings": {},
		"filled settings": {
			settings: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
			expected: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			copied := testCase.settings.Copy()

			assert.Equal(t, testCase.expected, copied)
		})
	}
}

func Test_Settings_OverrideWith(t *testing.T) {
	t.Parallel()

	someHandler := http.NewServeMux()
	someLogger := &testLogger{}

	testCases := map[string]struct {
		settings Settings
		other    Settings
		expected Settings
	}{
		"override empty with empty": {},
		"override empty with filled": {
			other: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
			expected: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
		},
		"override filled with empty": {
			settings: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
			expected: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
		},
		"override filled with filled": {
			settings: Settings{
				Address:           ":8001",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Second,
				ReadTimeout:       time.Second,
				ShutdownTimeout:   time.Second,
			},
			other: Settings{
				Address:           ":8002",
				ReadHeaderTimeout: time.Hour,
				ReadTimeout:       time.Hour,
				ShutdownTimeout:   time.Hour,
			},
			expected: Settings{
				Address:           ":8002",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Hour,
				ReadTimeout:       time.Hour,
				ShutdownTimeout:   time.Hour,
			},
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			testCase.settings.OverrideWith(testCase.other)

			assert.Equal(t, testCase.expected, testCase.settings)
		})
	}
}

func Test_Settings_Validate(t *testing.T) {
	t.Parallel()

	someHandler := http.NewServeMux()
	someLogger := &testLogger{}

	testCases := map[string]struct {
		settings   Settings
		errWrapped error
		errMessage string
	}{
		"bad_address": {
			settings: Settings{
				Address: "address:notanint",
			},
			errWrapped: validate.ErrPortNotAnInteger,
			errMessage: "port value is not an integer: notanint",
		},
		"nil handler": {
			settings: Settings{
				Address: ":8000",
			},
			errWrapped: ErrHandlerIsNotSet,
			errMessage: ErrHandlerIsNotSet.Error(),
		},
		"nil logger": {
			settings: Settings{
				Address: ":8000",
				Handler: someHandler,
			},
			errWrapped: ErrLoggerIsNotSet,
			errMessage: ErrLoggerIsNotSet.Error(),
		},
		"read header timeout too small": {
			settings: Settings{
				Address:           ":8000",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Nanosecond,
			},
			errWrapped: ErrReadHeaderTimeoutTooSmall,
			errMessage: "read header timeout is too small: 1ns must be at least 1ms",
		},
		"read timeout too small": {
			settings: Settings{
				Address:           ":8000",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Millisecond,
				ReadTimeout:       time.Nanosecond,
			},
			errWrapped: ErrReadTimeoutTooSmall,
			errMessage: "read timeout is too small: 1ns must be at least 1ms",
		},
		"shutdown timeout too small": {
			settings: Settings{
				Address:           ":8000",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Millisecond,
				ReadTimeout:       time.Millisecond,
				ShutdownTimeout:   time.Millisecond,
			},
			errWrapped: ErrShutdownTimeoutTooSmall,
			errMessage: "shutdown timeout is too small: 1ms must be at least 5ms",
		},
		"valid settings": {
			settings: Settings{
				Address:           ":8000",
				Handler:           someHandler,
				Logger:            someLogger,
				ReadHeaderTimeout: time.Millisecond,
				ReadTimeout:       time.Millisecond,
				ShutdownTimeout:   time.Second,
			},
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			err := testCase.settings.Validate()

			assert.ErrorIs(t, err, testCase.errWrapped)
			if testCase.errMessage != "" {
				assert.EqualError(t, err, testCase.errMessage)
			}
		})
	}
}

func Test_Settings_String(t *testing.T) {
	t.Parallel()

	testCases := map[string]struct {
		settings Settings
		s        string
	}{
		"all values": {
			settings: Settings{
				Address:           ":8000",
				ReadHeaderTimeout: time.Millisecond,
				ReadTimeout:       time.Millisecond,
				ShutdownTimeout:   time.Second,
			},
			s: `HTTP server settings:
├── Listening address: :8000
├── Read header timeout: 1ms
├── Read timeout: 1ms
└── Shutdown timeout: 1s`,
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			s := testCase.settings.String()

			assert.Equal(t, testCase.s, s)
		})
	}
}
