package checker

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/tidbcloud/service-proxy/pkg/checker/core"
)

func TestHandler_ServeHTTP(t *testing.T) {
	tests := []struct {
		name           string
		method         string
		body           string
		setupRegistry  func() *core.Registry
		wantStatusCode int
		wantErrMsg     string
	}{
		{
			name:   "POST success",
			method: http.MethodPost,
			body:   `{"targetType": "test"}`,
			setupRegistry: func() *core.Registry {
				r := core.NewRegistry()
				r.Register("test", func(req *core.CheckRequest) (core.Checker, error) {
					return &core.MockChecker{
						CheckFunc: func(ctx context.Context) error {
							return nil
						},
						TypeName: "test",
					}, nil
				})
				return r
			},
			wantStatusCode: http.StatusOK,
			wantErrMsg:     "",
		},
		{
			name:   "GET method not allowed",
			method: http.MethodGet,
			body:   "",
			setupRegistry: func() *core.Registry {
				return core.NewRegistry()
			},
			wantStatusCode: http.StatusMethodNotAllowed,
			wantErrMsg:     "",
		},
		{
			name:   "PUT method not allowed",
			method: http.MethodPut,
			body:   "",
			setupRegistry: func() *core.Registry {
				return core.NewRegistry()
			},
			wantStatusCode: http.StatusMethodNotAllowed,
			wantErrMsg:     "",
		},
		{
			name:   "POST with invalid JSON",
			method: http.MethodPost,
			body:   `{invalid json`,
			setupRegistry: func() *core.Registry {
				return core.NewRegistry()
			},
			wantStatusCode: http.StatusBadRequest,
			wantErrMsg:     "invalid request body",
		},
		{
			name:   "POST with empty body",
			method: http.MethodPost,
			body:   "",
			setupRegistry: func() *core.Registry {
				return core.NewRegistry()
			},
			wantStatusCode: http.StatusBadRequest,
			wantErrMsg:     "",
		},
		{
			name:   "POST with missing targetType",
			method: http.MethodPost,
			body:   `{}`,
			setupRegistry: func() *core.Registry {
				return core.NewRegistry()
			},
			wantStatusCode: http.StatusBadRequest,
			wantErrMsg:     "type is required",
		},
		{
			name:   "POST with unknown targetType",
			method: http.MethodPost,
			body:   `{"targetType": "unknown"}`,
			setupRegistry: func() *core.Registry {
				return core.NewRegistry()
			},
			wantStatusCode: http.StatusBadRequest,
			wantErrMsg:     "unknown checker type",
		},
		{
			name:   "POST with factory error",
			method: http.MethodPost,
			body:   `{"targetType": "error"}`,
			setupRegistry: func() *core.Registry {
				r := core.NewRegistry()
				r.Register("error", func(req *core.CheckRequest) (core.Checker, error) {
					return nil, errors.New("factory error")
				})
				return r
			},
			wantStatusCode: http.StatusBadRequest,
			wantErrMsg:     "factory error",
		},
		{
			name:   "POST with check failure",
			method: http.MethodPost,
			body:   `{"targetType": "fail"}`,
			setupRegistry: func() *core.Registry {
				r := core.NewRegistry()
				r.Register("fail", func(req *core.CheckRequest) (core.Checker, error) {
					return &core.MockChecker{
						CheckFunc: func(ctx context.Context) error {
							return errors.New("connection failed")
						},
						TypeName: "fail",
					}, nil
				})
				return r
			},
			wantStatusCode: http.StatusBadRequest,
			wantErrMsg:     "connection failed",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			registry := tt.setupRegistry()
			handler := NewHandler(registry, nil)

			var body *bytes.Buffer
			if tt.body != "" {
				body = bytes.NewBufferString(tt.body)
			} else {
				body = bytes.NewBuffer(nil)
			}

			req := httptest.NewRequest(tt.method, "/check", body)
			rec := httptest.NewRecorder()

			handler.ServeHTTP(rec, req)

			if rec.Code != tt.wantStatusCode {
				t.Errorf("expected status code %d, got %d", tt.wantStatusCode, rec.Code)
			}

			if tt.wantErrMsg != "" {
				var resp ErrorResponse
				if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
					t.Fatalf("failed to decode response: %v", err)
				}
				if !strings.Contains(resp.Error, tt.wantErrMsg) {
					t.Errorf("error %q does not contain %q", resp.Error, tt.wantErrMsg)
				}
			}
		})
	}
}

func TestHandler_ResponseFormat(t *testing.T) {
	tests := []struct {
		name           string
		setupRegistry  func() *core.Registry
		body           string
		wantStatusCode int
		checkResponse  func(*testing.T, *httptest.ResponseRecorder)
	}{
		{
			name: "success response has no body",
			setupRegistry: func() *core.Registry {
				r := core.NewRegistry()
				r.Register("test", func(req *core.CheckRequest) (core.Checker, error) {
					return &core.MockChecker{
						CheckFunc: func(ctx context.Context) error { return nil },
						TypeName:  "test",
					}, nil
				})
				return r
			},
			body:           `{"targetType": "test"}`,
			wantStatusCode: http.StatusOK,
			checkResponse: func(t *testing.T, rec *httptest.ResponseRecorder) {
				// Success response should have empty body or no content
				if rec.Body.Len() > 0 {
					// If there is content, it should be valid JSON or empty
					body := rec.Body.String()
					if body != "" && body != "\n" {
						t.Logf("success response has body: %q", body)
					}
				}
			},
		},
		{
			name: "error response is JSON with error field",
			setupRegistry: func() *core.Registry {
				r := core.NewRegistry()
				r.Register("fail", func(req *core.CheckRequest) (core.Checker, error) {
					return &core.MockChecker{
						CheckFunc: func(ctx context.Context) error {
							return errors.New("check failed")
						},
						TypeName: "fail",
					}, nil
				})
				return r
			},
			body:           `{"targetType": "fail"}`,
			wantStatusCode: http.StatusBadRequest,
			checkResponse: func(t *testing.T, rec *httptest.ResponseRecorder) {
				contentType := rec.Header().Get("Content-Type")
				if contentType != "application/json" {
					t.Errorf("expected Content-Type application/json, got %q", contentType)
				}

				var resp ErrorResponse
				if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
					t.Fatalf("failed to decode response: %v", err)
				}

				if resp.Error == "" {
					t.Error("expected non-empty error field")
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			handler := NewHandler(tt.setupRegistry(), nil)

			req := httptest.NewRequest(http.MethodPost, "/check", bytes.NewBufferString(tt.body))
			rec := httptest.NewRecorder()

			handler.ServeHTTP(rec, req)

			if rec.Code != tt.wantStatusCode {
				t.Errorf("expected status code %d, got %d", tt.wantStatusCode, rec.Code)
			}

			if tt.checkResponse != nil {
				tt.checkResponse(t, rec)
			}
		})
	}
}

func TestHandler_RequestBodyTooLarge(t *testing.T) {
	// Test that requests larger than maxCheckRequestSize (64KB) are rejected with 413
	registry := core.NewRegistry()
	registry.Register("test", func(req *core.CheckRequest) (core.Checker, error) {
		return &core.MockChecker{
			CheckFunc: func(ctx context.Context) error { return nil },
			TypeName:  "test",
		}, nil
	})

	handler := NewHandler(registry, nil)

	// Create a valid JSON body that's larger than 64KB.
	// The body must look like valid JSON initially so the decoder doesn't fail
	// before reaching the MaxBytesReader limit.
	// Use a structure like: {"targetType":"test","data":"xxx...xxx"}
	// where the xxx part is large enough to exceed 64KB total.
	largeData := strings.Repeat("x", 70*1024) // 70KB of x's
	largeBody := `{"targetType":"test","data":"` + largeData + `"}`

	req := httptest.NewRequest(http.MethodPost, "/check", bytes.NewBufferString(largeBody))
	rec := httptest.NewRecorder()

	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusRequestEntityTooLarge {
		t.Errorf("expected status code %d, got %d", http.StatusRequestEntityTooLarge, rec.Code)
	}

	var resp ErrorResponse
	if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if !strings.Contains(resp.Error, "too large") {
		t.Errorf("error %q does not contain 'too large'", resp.Error)
	}
}

func TestHandler_RequestBodyAtLimit(t *testing.T) {
	// Test that requests at exactly 64KB work fine (when valid JSON)
	registry := core.NewRegistry()
	registry.Register("test", func(req *core.CheckRequest) (core.Checker, error) {
		return &core.MockChecker{
			CheckFunc: func(ctx context.Context) error { return nil },
			TypeName:  "test",
		}, nil
	})

	handler := NewHandler(registry, nil)

	// Create a valid JSON body that's under the limit
	// A real check request is small, so this test just verifies normal requests work
	validBody := `{"targetType": "test"}`
	req := httptest.NewRequest(http.MethodPost, "/check", bytes.NewBufferString(validBody))
	rec := httptest.NewRecorder()

	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Errorf("expected status code %d, got %d", http.StatusOK, rec.Code)
	}
}
