package mcpproxy

import (
	"crypto/rsa"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/sigbit/mcp-auth-proxy/v2/pkg/proxy"
	"github.com/stretchr/testify/require"
)

func TestRun_NormalizesExternalURLTrailingSlash(t *testing.T) {
	originalNewProxyRouter := newProxyRouter
	t.Cleanup(func() {
		newProxyRouter = originalNewProxyRouter
	})

	cases := []struct {
		name        string
		input       string
		wantURL     string
		wantErr     bool
		errContains string
	}{
		{name: "no trailing slash", input: "https://example.com", wantURL: "https://example.com/"},
		{name: "with trailing slash", input: "https://example.com/", wantURL: "https://example.com/"},
		{name: "with path", input: "https://example.com/foo", wantErr: true, errContains: "must not have a path"},
	}

	for _, tt := range cases {
		t.Run(tt.name, func(t *testing.T) {
			var receivedURL string
			newProxyRouter = func(externalURL string, proxyHandler http.Handler, publicKey *rsa.PublicKey, proxyHeaders http.Header, httpStreamingOnly bool, forwardAuthorizationHeader bool, headerMapping map[string]string, headerMappingBase string) (*proxy.ProxyRouter, error) {
				receivedURL = externalURL
				return nil, errors.New("stop early")
			}

			err := Run(
				":0", ":0", false, "", "", false, "", "",
				t.TempDir(), "local", "",
				tt.input,
				"", "", nil, nil,
				"", "", "", "", nil, nil,
				"", "", "", nil, "", "", nil, nil, nil, nil,
				false, "", "", nil, nil, "", false,
				[]string{"http://example.com"}, false, nil, "/userinfo",
			)

			if tt.wantErr {
				require.Error(t, err)
				require.Contains(t, err.Error(), tt.errContains)
				return
			}

			require.Error(t, err)
			require.Contains(t, err.Error(), "stop early")
			require.Equal(t, tt.wantURL, receivedURL)
		})
	}
}

func TestRun_PassesHTTPStreamingOnlyToProxyRouter(t *testing.T) {
	originalNewProxyRouter := newProxyRouter
	t.Cleanup(func() {
		newProxyRouter = originalNewProxyRouter
	})

	var streamingOnlyReceived bool
	newProxyRouter = func(externalURL string, proxyHandler http.Handler, publicKey *rsa.PublicKey, proxyHeaders http.Header, httpStreamingOnly bool, forwardAuthorizationHeader bool, headerMapping map[string]string, headerMappingBase string) (*proxy.ProxyRouter, error) {
		streamingOnlyReceived = httpStreamingOnly
		return nil, errors.New("proxy router init failed")
	}

	err := Run(
		":0",
		":0",
		false,
		"",
		"",
		false,
		"",
		"",
		t.TempDir(),
		"local",
		"",
		"http://localhost",
		"",
		"",
		nil,
		nil,
		"",
		"",
		"",
		"",
		nil,
		nil,
		"",
		"",
		"",
		nil,
		"",
		"",
		nil,
		nil,
		nil,
		nil,
		false,
		"",
		"",
		nil,
		nil,
		"",
		false,
		[]string{"http://example.com"},
		true,
		nil,
		"/userinfo",
	)

	require.Error(t, err)
	require.Contains(t, err.Error(), "failed to create proxy router")
	require.True(t, streamingOnlyReceived, "httpStreamingOnly should be forwarded to proxy router")
}

func TestSessionCookieSecure(t *testing.T) {
	cases := []struct {
		externalURL string
		want        bool
	}{
		{externalURL: "https://example.com", want: true},
		{externalURL: "http://example.com", want: false},
	}

	for _, tt := range cases {
		t.Run(tt.externalURL, func(t *testing.T) {
			parsedURL, err := url.Parse(tt.externalURL)
			require.NoError(t, err)
			require.Equal(t, tt.want, sessionCookieSecure(parsedURL))
		})
	}
}

func TestUserInfoFieldsFromConfig(t *testing.T) {
	t.Run("extracts fields from header mapping and user ID field", func(t *testing.T) {
		fields := userInfoFieldsFromConfig("/email", map[string]string{
			"/email":              "X-Forwarded-Email",
			"/preferred_username": "X-Forwarded-User",
		})
		require.ElementsMatch(t, []string{"email", "preferred_username"}, fields)
	})

	t.Run("handles nested JSON pointers by taking top-level key", func(t *testing.T) {
		fields := userInfoFieldsFromConfig("/email", map[string]string{
			"/address/street": "X-Street",
		})
		require.ElementsMatch(t, []string{"email", "address"}, fields)
	})

	t.Run("deduplicates overlapping fields", func(t *testing.T) {
		fields := userInfoFieldsFromConfig("/email", map[string]string{
			"/email": "X-Forwarded-Email",
		})
		require.Equal(t, []string{"email"}, fields)
	})

	t.Run("empty config returns empty slice", func(t *testing.T) {
		fields := userInfoFieldsFromConfig("", nil)
		require.Empty(t, fields)
	})

	t.Run("handles user ID field without leading slash", func(t *testing.T) {
		fields := userInfoFieldsFromConfig("email", nil)
		require.Equal(t, []string{"email"}, fields)
	})
}

func TestHealthzEndpoint(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Register healthz before auth middleware, same as in Run()
	router.GET("/healthz", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	// Add a catch-all that returns 401 to simulate auth middleware
	router.Use(func(c *gin.Context) {
		c.AbortWithStatus(http.StatusUnauthorized)
	})

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/healthz", nil)
	router.ServeHTTP(w, req)

	require.Equal(t, http.StatusOK, w.Code)

	var body map[string]string
	err := json.Unmarshal(w.Body.Bytes(), &body)
	require.NoError(t, err)
	require.Equal(t, "ok", body["status"])
}
