package server

import (
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"slices"
	"testing"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
)

type mockAuthHandler struct {
	oauthCallbackCalled bool
	middlewareCalled    bool
}

func (m *mockAuthHandler) oauthRedirectCallback(w http.ResponseWriter, r *http.Request) {
	m.oauthCallbackCalled = true
	if r.Method != http.MethodGet {
		http.Error(w, "OAuth Redirect Callback - Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("OAuth Redirect Callback"))
}

func (m *mockAuthHandler) middleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		m.middlewareCalled = true
		next(w, r)
	}
}

type mockTunnelHandler struct {
	tunnelCalled bool
}

func (m *mockTunnelHandler) tunnel(w http.ResponseWriter, r *http.Request) {
	m.tunnelCalled = true
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("User workspace tunnel"))
}

func createTestServer(t *testing.T, apiURL string) (*httpServer, *mockAuthHandler, *mockTunnelHandler) {
	t.Helper()

	parsedURL, err := url.Parse(apiURL)
	if err != nil {
		t.Fatalf("Failed to create test server: %v", err)
	}

	auth := &mockAuthHandler{}
	tunnel := &mockTunnelHandler{}

	server := &httpServer{
		log:                 testlogger.New(t),
		authHandler:         auth,
		tunnelHandler:       tunnel,
		idleTimeout:         30 * time.Second,
		listenerGracePeriod: 5 * time.Second,
		readHeaderTimeout:   10 * time.Second,
		shutdownTimeout:     30 * time.Second,
		apiExternalURL:      parsedURL,
	}

	return server, auth, tunnel
}

func TestHTTPRouting(t *testing.T) {
	userWorkspaceHost := "workspaces.test"
	apiExternalHost := "gdk.test"
	apiExternalURLPath := "/-/kubernetes-agent/workspaces"
	apiExternalURL := (&url.URL{
		Scheme: "https",
		Host:   net.JoinHostPort(apiExternalHost, "3333"),
		Path:   apiExternalURLPath,
	}).String()

	tests := []struct {
		name                          string
		method                        string
		path                          string
		host                          string
		expectedStatusCode            int
		expectedBody                  string
		expectedOAuthCallbackCalled   bool
		expectedOAuthMiddlewareCalled bool
		expectTunnelHandlerCalled     bool
	}{
		{
			name:                          "OAuth redirect callback - exact match",
			method:                        http.MethodGet,
			path:                          apiExternalURLPath + oauthRedirectCallbackPath,
			host:                          apiExternalHost,
			expectedStatusCode:            http.StatusOK,
			expectedBody:                  "OAuth Redirect Callback",
			expectedOAuthCallbackCalled:   true,
			expectedOAuthMiddlewareCalled: false,
			expectTunnelHandlerCalled:     false,
		},
		{
			name:                          "OAuth redirect callback - exact match but wrong method",
			method:                        http.MethodPost,
			path:                          apiExternalURLPath + oauthRedirectCallbackPath,
			host:                          apiExternalHost,
			expectedStatusCode:            http.StatusMethodNotAllowed,
			expectedBody:                  "OAuth Redirect Callback - Method Not Allowed\n",
			expectedOAuthCallbackCalled:   true,
			expectedOAuthMiddlewareCalled: false,
			expectTunnelHandlerCalled:     false,
		},
		{
			name:                          "API base path - returns 404",
			method:                        http.MethodPost,
			path:                          apiExternalURLPath,
			host:                          apiExternalHost,
			expectedStatusCode:            http.StatusNotFound,
			expectedBody:                  "404 page not found\n",
			expectedOAuthCallbackCalled:   false,
			expectedOAuthMiddlewareCalled: false,
			expectTunnelHandlerCalled:     false,
		},
		{
			name:                          "User's workspace path - tunnel handler",
			method:                        http.MethodGet,
			path:                          "/",
			host:                          userWorkspaceHost,
			expectedStatusCode:            http.StatusOK,
			expectedBody:                  "User workspace tunnel",
			expectedOAuthCallbackCalled:   false,
			expectedOAuthMiddlewareCalled: true,
			expectTunnelHandlerCalled:     true,
		},
		{
			name:                          "User's workspace path with subpath - tunnel handler",
			method:                        http.MethodGet,
			path:                          "/example",
			host:                          userWorkspaceHost,
			expectedStatusCode:            http.StatusOK,
			expectedBody:                  "User workspace tunnel",
			expectedOAuthCallbackCalled:   false,
			expectedOAuthMiddlewareCalled: true,
			expectTunnelHandlerCalled:     true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server, mockAuth, mockTunnel := createTestServer(t, apiExternalURL)
			handler := server.constructHandler()

			req, err := http.NewRequestWithContext(t.Context(), tt.method, tt.path, nil)
			if err != nil {
				t.Fatalf("Failed to create http test request: %v", err)
			}
			req.Host = tt.host
			w := httptest.NewRecorder()
			handler.ServeHTTP(w, req)

			if w.Code != tt.expectedStatusCode {
				t.Errorf("Expected status %d, got %d", tt.expectedStatusCode, w.Code)
			}
			if w.Body.String() != tt.expectedBody {
				t.Errorf("Expected body %s, got %s", tt.expectedBody, w.Body.String())
			}
			if mockAuth.oauthCallbackCalled != tt.expectedOAuthCallbackCalled {
				t.Errorf("Expected oauth callback called %v, got %v", tt.expectedOAuthCallbackCalled, mockAuth.oauthCallbackCalled)
			}
			if mockAuth.middlewareCalled != tt.expectedOAuthMiddlewareCalled {
				t.Errorf("Expected oauth middleware called %v, got %v", tt.expectedOAuthMiddlewareCalled, mockAuth.middlewareCalled)
			}
			if mockTunnel.tunnelCalled != tt.expectTunnelHandlerCalled {
				t.Errorf("Expected tunnel called %v, got %v", tt.expectTunnelHandlerCalled, mockTunnel.tunnelCalled)
			}
		})
	}
}

func TestCompileMiddlewareWithMultipleMiddlewares(t *testing.T) {
	var callOrder []string

	middleware1 := func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			callOrder = append(callOrder, "middleware1")
			next(w, r)
		}
	}
	middleware2 := func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			callOrder = append(callOrder, "middleware2")
			next(w, r)
		}
	}
	handler := func(w http.ResponseWriter, r *http.Request) {
		callOrder = append(callOrder, "handler")
		w.WriteHeader(http.StatusOK)
	}

	// Test middleware compilation
	compiled := compileMiddleware(handler, []middleware{middleware1, middleware2})

	callOrder = []string{}

	// Execute
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	w := httptest.NewRecorder()
	compiled(w, req)

	// Check that middleware are called in the correct order
	expectedOrder := []string{"middleware1", "middleware2", "handler"}
	if !slices.Equal(callOrder, expectedOrder) {
		t.Errorf("Expected call order %s, got %s", expectedOrder, callOrder)
	}
}

func TestCompileMiddlewareWithNoMiddleware(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("Hello"))
	}

	compiled := compileMiddleware(handler, []middleware{})

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	w := httptest.NewRecorder()
	compiled(w, req)

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

	if w.Body.String() != "Hello" {
		t.Errorf("Expected body \"Hello\", got %s", w.Body.String())
	}
}
