package controller

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

	"github.com/gin-gonic/gin"
	"github.com/lestrrat-go/jwx/v3/jwa"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/pocket-id/pocket-id/backend/internal/common"
	"github.com/pocket-id/pocket-id/backend/internal/service"
	jwkutils "github.com/pocket-id/pocket-id/backend/internal/utils/jwk"
)

func newMinimalJwtService(t *testing.T) *service.JwtService {
	t.Helper()

	key, err := jwkutils.GenerateKey(jwa.RS256().String(), "")
	require.NoError(t, err, "failed to generate test JWK key")

	svc := &service.JwtService{}
	require.NoError(t, svc.SetKey(key), "failed to set JWK key on JwtService")
	return svc
}

func TestClientIDMetadataDocumentDiscoveryFollowsAllowlist(t *testing.T) {
	origURL := common.EnvConfig.AppURL
	t.Cleanup(func() {
		common.EnvConfig.AppURL = origURL
	})

	common.EnvConfig.AppURL = "https://test.example.com"
	jwtSvc := newMinimalJwtService(t)
	cimdURLAllowlist := []string(nil)
	wkc := &WellKnownController{
		jwtService: jwtSvc,
		getCIMDURLAllowlist: func() []string {
			return cimdURLAllowlist
		},
	}

	parse := func(t *testing.T) map[string]any {
		t.Helper()
		raw, err := wkc.computeServerMetadata()
		require.NoError(t, err)
		var cfg map[string]any
		require.NoError(t, json.Unmarshal(raw, &cfg))
		return cfg
	}

	cimdURLAllowlist = []string{"https://client.example.com/**"}
	assert.Equal(t, true, parse(t)["client_id_metadata_document_supported"])

	cimdURLAllowlist = nil
	assert.Equal(t, false, parse(t)["client_id_metadata_document_supported"])
}

func TestOAuthAuthorizationServerMetadata(t *testing.T) {
	gin.SetMode(gin.TestMode)

	origAppURL := common.EnvConfig.AppURL
	origInternalAppURL := common.EnvConfig.InternalAppURL
	t.Cleanup(func() {
		common.EnvConfig.AppURL = origAppURL
		common.EnvConfig.InternalAppURL = origInternalAppURL
	})
	common.EnvConfig.AppURL = "https://test.example.com"
	common.EnvConfig.InternalAppURL = "https://test.example.com"

	router := gin.New()
	NewWellKnownController(router.Group("/"), newMinimalJwtService(t), func() []string { return nil })

	get := func(t *testing.T, path string) *httptest.ResponseRecorder {
		t.Helper()
		req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, path, http.NoBody)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
		return w
	}

	w := get(t, "/.well-known/oauth-authorization-server")
	require.Equal(t, http.StatusOK, w.Code)
	require.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))

	var doc map[string]any
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &doc))

	assert.Equal(t, common.EnvConfig.AppURL, doc["issuer"])
	assert.Equal(t, common.EnvConfig.AppURL+"/authorize", doc["authorization_endpoint"])
	assert.Equal(t, common.EnvConfig.InternalAppURL+"/api/oidc/token", doc["token_endpoint"])
	assert.Contains(t, doc["response_types_supported"], "code")
	assert.NotEmpty(t, doc["jwks_uri"])
	assert.Contains(t, doc["scopes_supported"], "openid")
	assert.Contains(t, doc["grant_types_supported"], "authorization_code")
	assert.Contains(t, doc["code_challenge_methods_supported"], "S256")
	assert.Equal(t, "https://pocket-id.org/docs", doc["service_documentation"])
	assert.ElementsMatch(t, []any{"query", "fragment", "form_post"}, doc["response_modes_supported"])
	assert.NotContains(t, doc, "revocation_endpoint")
	assert.NotContains(t, doc, "registration_endpoint")

	for name, value := range doc {
		if arr, ok := value.([]any); ok {
			assert.NotEmpty(t, arr, "metadata member %q must be omitted when it has no values", name)
		}
	}

	assert.JSONEq(t, get(t, "/.well-known/openid-configuration").Body.String(), w.Body.String())
}
