package auth

import (
	"bytes"
	"encoding/json"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/observability/logging"
)

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

	handler := &OIDCHandler{}
	router := gin.New()
	router.GET("/api/v1/auth/oidc/discovery", handler.Discovery)

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/auth/oidc/discovery", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusServiceUnavailable, recorder.Code)
	require.Contains(t, recorder.Body.String(), "PingCAP User Center is not enabled")
}

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

	config := PingCAPAuthConfig{
		ClientID:            "legacy-web",
		ClientSecret:        "legacy-secret",
		WebClientID:         "agentteam-web",
		WebClientSecret:     "web-secret",
		DesktopClientID:     "agentteam-desktop",
		DesktopClientSecret: "",
	}

	desktopClient := config.oidcClientForRedirectURI("http://127.0.0.1:60437/oidc/callback")
	require.Equal(t, "agentteam-desktop", desktopClient.clientID)
	require.Empty(t, desktopClient.clientSecret)

	ipv6DesktopClient := config.oidcClientForRedirectURI("http://[::1]:60437/oidc/callback")
	require.Equal(t, "agentteam-desktop", ipv6DesktopClient.clientID)

	webClient := config.oidcClientForRedirectURI("https://agentteam.example.com/oidc/callback")
	require.Equal(t, "agentteam-web", webClient.clientID)
	require.Equal(t, "web-secret", webClient.clientSecret)
}

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

	config := PingCAPAuthConfig{
		ClientID:     "agentteam",
		ClientSecret: "legacy-secret",
	}

	webClient := config.oidcClientForRedirectURI("https://agentteam.example.com/oidc/callback")
	require.Equal(t, "agentteam", webClient.clientID)
	require.Equal(t, "legacy-secret", webClient.clientSecret)
}

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

	const sensitiveBody = "token=secret-token user@example.com https://agentteam.example.com/oidc/callback"
	provider := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		switch request.URL.Path {
		case "/.well-known/openid-configuration":
			require.NoError(t, json.NewEncoder(writer).Encode(map[string]any{
				"issuer":                 "test",
				"authorization_endpoint": providerURL(request) + "/authorize",
				"token_endpoint":         providerURL(request) + "/token",
				"userinfo_endpoint":      providerURL(request) + "/userinfo",
			}))
		case "/token":
			writer.WriteHeader(http.StatusUnauthorized)
			_, _ = writer.Write([]byte(sensitiveBody))
		default:
			http.NotFound(writer, request)
		}
	}))
	defer provider.Close()

	var output bytes.Buffer
	logger := slog.New(logging.WrapHandler(slog.NewJSONHandler(&output, nil))).With(slog.String("domain", "auth"))
	handler := newRouteHandlers(Dependencies{
		Logger: logger,
		PingCAPAuthConfigProvider: func() PingCAPAuthConfig {
			return PingCAPAuthConfig{
				Enabled: true, Issuer: provider.URL, WebClientID: "web-client",
				RedirectURI: "https://agentteam.example.com/oidc/callback",
			}
		},
		HTTPClient: provider.Client(),
	}).oidc
	router := gin.New()
	router.POST("/api/v1/auth/oidc/callback", handler.Callback)

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/oidc/callback", strings.NewReader(`{"code":"code","code_verifier":"verifier"}`))
	request.Header.Set("Content-Type", "application/json")
	request = request.WithContext(logging.WithRequestID(request.Context(), "req_oidc"))
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusUnauthorized, recorder.Code)
	var entry map[string]any
	require.NoError(t, json.Unmarshal(bytes.TrimSpace(output.Bytes()), &entry))
	require.Equal(t, "auth", entry["domain"])
	require.Equal(t, "req_oidc", entry["request_id"])
	require.Equal(t, "token_exchange_failed", entry["error_class"])
	require.NotContains(t, output.String(), sensitiveBody)
	require.NotContains(t, output.String(), "secret-token")
	require.NotContains(t, output.String(), "user@example.com")
	require.NotContains(t, output.String(), "redirect_uri")
	require.NotContains(t, entry, "error")
}

func providerURL(request *http.Request) string {
	return "http://" + request.Host
}
