package auth

import (
	"strings"
	"testing"
)

type adminDCRScopePolicyStub map[string]bool

func (s adminDCRScopePolicyStub) AllowsPublicOAuthScope(scope string) bool { return s[scope] }

func TestBuildAdminDynamicClientRegistrationRequiresExplicitPublicClientAndScope(t *testing.T) {
	policy := adminDCRScopePolicyStub{"records.read": true, "openid": true}
	registration, err := BuildAdminDynamicClientRegistration(DynamicClientRegistrationRequest{
		ClientName:              "MCP desktop",
		RedirectURIs:            []string{"https://desktop.example/callback"},
		GrantTypes:              []string{OAuthGrantRefreshToken, OAuthGrantAuthorizationCode},
		ResponseTypes:           []string{"code"},
		Scope:                   "records.read openid records.read",
		TokenEndpointAuthMethod: "none",
	}, policy)
	if err != nil {
		t.Fatalf("BuildAdminDynamicClientRegistration: %v", err)
	}
	if !strings.HasPrefix(registration.ClientID, dynamicClientIDPrefix) {
		t.Fatalf("client id = %q, want %q prefix", registration.ClientID, dynamicClientIDPrefix)
	}
	if registration.Scope != "openid records.read" || strings.Join(registration.GrantTypes, " ") != "authorization_code refresh_token" {
		t.Fatalf("registration = %#v, want normalized approved scope and grants", registration)
	}

	for _, request := range []DynamicClientRegistrationRequest{
		{RedirectURIs: []string{"https://desktop.example/callback"}, Scope: "records.read"},
		{RedirectURIs: []string{"https://desktop.example/callback"}, Scope: "records.read", TokenEndpointAuthMethod: "client_secret_basic"},
		{RedirectURIs: []string{"https://desktop.example/callback"}, TokenEndpointAuthMethod: "none"},
		{RedirectURIs: []string{"https://desktop.example/callback"}, Scope: "records.write", TokenEndpointAuthMethod: "none"},
	} {
		if _, err := BuildAdminDynamicClientRegistration(request, policy); err == nil {
			t.Fatalf("request %#v unexpectedly registered", request)
		}
	}
}

func TestBuildAdminDynamicClientRegistrationAllowsAdminReviewedLoopbackRedirect(t *testing.T) {
	registration, err := BuildAdminDynamicClientRegistration(DynamicClientRegistrationRequest{
		RedirectURIs:            []string{"http://127.0.0.1:4242/callback"},
		Scope:                   "records.read",
		TokenEndpointAuthMethod: "none",
	}, adminDCRScopePolicyStub{"records.read": true})
	if err != nil {
		t.Fatalf("admin-reviewed loopback registration: %v", err)
	}
	if registration == nil || len(registration.RedirectURIs) != 1 {
		t.Fatalf("registration = %#v, want accepted loopback callback", registration)
	}
}

func TestBuildAdminDynamicClientRegistrationCanonicalizesScopeCase(t *testing.T) {
	registration, err := BuildAdminDynamicClientRegistration(DynamicClientRegistrationRequest{
		RedirectURIs:            []string{"https://desktop.example/callback"},
		Scope:                   "Records.Read records.read",
		TokenEndpointAuthMethod: "none",
	}, adminDCRScopePolicyStub{"records.read": true})
	if err != nil {
		t.Fatalf("BuildAdminDynamicClientRegistration: %v", err)
	}
	if registration.Scope != "records.read" {
		t.Fatalf("scope = %q, want canonical lower-case scope", registration.Scope)
	}
}

type adminDCRPackScopePolicyStub struct {
	adminDCRScopePolicyStub
	packScopes map[string]bool
}

func (s adminDCRPackScopePolicyStub) AllowsPublicOAuthScopeForPack(scope, packName string) bool {
	return s.packScopes[packName+":"+scope]
}

func TestBuildAdminDynamicClientRegistrationBindsSourceOwnedScopeToPack(t *testing.T) {
	policy := adminDCRPackScopePolicyStub{
		adminDCRScopePolicyStub: adminDCRScopePolicyStub{},
		packScopes:              map[string]bool{"crm-pack:external.records.read": true},
	}
	registration, err := BuildAdminDynamicClientRegistration(DynamicClientRegistrationRequest{
		RedirectURIs:            []string{"https://desktop.example/callback"},
		Scope:                   "external.records.read",
		SourcePackName:          " crm-pack ",
		TokenEndpointAuthMethod: "none",
	}, policy)
	if err != nil {
		t.Fatalf("BuildAdminDynamicClientRegistration: %v", err)
	}
	if registration.Scope != "external.records.read" || registration.SourcePackName != "crm-pack" {
		t.Fatalf("registration = %#v, want source-pack-bound scope", registration)
	}

	if _, err := BuildAdminDynamicClientRegistration(DynamicClientRegistrationRequest{
		RedirectURIs: []string{"https://desktop.example/callback"}, Scope: "external.records.read", TokenEndpointAuthMethod: "none",
	}, policy); err == nil {
		t.Fatal("ownerless request unexpectedly registered source-owned scope")
	}
}

func TestBuildAdminDynamicClientRegistrationDefaultsToAuthorizationCodeOnly(t *testing.T) {
	registration, err := BuildAdminDynamicClientRegistration(DynamicClientRegistrationRequest{
		RedirectURIs:            []string{"https://desktop.example/callback"},
		Scope:                   "records.read",
		TokenEndpointAuthMethod: "none",
	}, adminDCRScopePolicyStub{"records.read": true})
	if err != nil {
		t.Fatalf("BuildAdminDynamicClientRegistration: %v", err)
	}
	if got := strings.Join(registration.GrantTypes, " "); got != OAuthGrantAuthorizationCode {
		t.Fatalf("default grant types = %q, want authorization_code only", got)
	}
}
