package healthhttp_test

import (
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"gitea.dwysokinski.me/twhelp/corev3/internal/health"
	"gitea.dwysokinski.me/twhelp/corev3/internal/health/healthhttp"
	"gitea.dwysokinski.me/twhelp/corev3/internal/health/healthtest"
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

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

	tests := []struct {
		name             string
		options          []health.Option
		expectedStatus   int
		expectedResponse healthhttp.Response
	}{
		{
			name: "pass",
			options: []health.Option{
				health.WithReadyCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithReadyCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithReadyCheck(healthtest.Checker{Nam: "test2", Err: nil}),
			},
			expectedStatus: http.StatusOK,
			expectedResponse: healthhttp.Response{
				Status: health.StatusPass.String(),
				Checks: map[string][]healthhttp.SingleCheckResult{
					"test": {
						{
							Status: health.StatusPass.String(),
						},
						{
							Status: health.StatusPass.String(),
						},
					},
					"test2": {
						{
							Status: health.StatusPass.String(),
						},
					},
				},
			},
		},
		{
			name: "2 checks passed, 1 check failed",
			options: []health.Option{
				health.WithReadyCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithReadyCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithReadyCheck(healthtest.Checker{Nam: "test2", Err: errors.New("failed")}),
			},
			expectedStatus: http.StatusServiceUnavailable,
			expectedResponse: healthhttp.Response{
				Status: health.StatusFail.String(),
				Checks: map[string][]healthhttp.SingleCheckResult{
					"test": {
						{
							Status: health.StatusPass.String(),
						},
						{
							Status: health.StatusPass.String(),
						},
					},
					"test2": {
						{
							Status:        health.StatusFail.String(),
							FailureReason: "failed",
						},
					},
				},
			},
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			rr := httptest.NewRecorder()

			healthhttp.ReadyHandler(health.New(tt.options...)).ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/readyz", nil))

			assert.Equal(t, tt.expectedStatus, rr.Code)
			assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
			var body healthhttp.Response
			require.NoError(t, json.NewDecoder(rr.Body).Decode(&body))
			assert.Empty(t, cmp.Diff(
				tt.expectedResponse,
				body,
				cmpopts.IgnoreTypes(time.Time{}),
			))
		})
	}
}

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

	tests := []struct {
		name             string
		options          []health.Option
		expectedStatus   int
		expectedResponse healthhttp.Response
	}{
		{
			name: "pass",
			options: []health.Option{
				health.WithLiveCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithLiveCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithLiveCheck(healthtest.Checker{Nam: "test2", Err: nil}),
			},
			expectedStatus: http.StatusOK,
			expectedResponse: healthhttp.Response{
				Status: health.StatusPass.String(),
				Checks: map[string][]healthhttp.SingleCheckResult{
					"test": {
						{
							Status: health.StatusPass.String(),
						},
						{
							Status: health.StatusPass.String(),
						},
					},
					"test2": {
						{
							Status: health.StatusPass.String(),
						},
					},
				},
			},
		},
		{
			name: "2 checks passed, 1 check failed",
			options: []health.Option{
				health.WithLiveCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithLiveCheck(healthtest.Checker{Nam: "test", Err: nil}),
				health.WithLiveCheck(healthtest.Checker{Nam: "test2", Err: errors.New("failed")}),
			},
			expectedStatus: http.StatusServiceUnavailable,
			expectedResponse: healthhttp.Response{
				Status: health.StatusFail.String(),
				Checks: map[string][]healthhttp.SingleCheckResult{
					"test": {
						{
							Status: health.StatusPass.String(),
						},
						{
							Status: health.StatusPass.String(),
						},
					},
					"test2": {
						{
							Status:        health.StatusFail.String(),
							FailureReason: "failed",
						},
					},
				},
			},
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			rr := httptest.NewRecorder()

			healthhttp.LiveHandler(health.New(tt.options...)).ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/livez", nil))

			assert.Equal(t, tt.expectedStatus, rr.Code)
			assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
			var body healthhttp.Response
			require.NoError(t, json.NewDecoder(rr.Body).Decode(&body))
			assert.Empty(t, cmp.Diff(
				tt.expectedResponse,
				body,
				cmpopts.IgnoreTypes(time.Time{}),
			))
		})
	}
}
