package auth

import (
	"crypto/sha256"
	"encoding/base64"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

func TestComputePublicEffectiveScopeDefaultsOnlySoleAllowedScope(t *testing.T) {
	publicType := OAuthClientTypePublic
	allowed := "external.records.read"
	app := &models.SystemConnectedApp{
		ClientType: &publicType,
		Scopes:     &allowed,
	}

	if _, err := computePublicEffectiveScope(app, allowed, nil); err == nil {
		t.Fatal("computePublicEffectiveScope succeeded without metadata scope policy, want error")
	}

	provider := publicScopeProviderStub{allowed: true}
	if got, err := computePublicEffectiveScope(app, allowed, provider); err != nil || got != allowed {
		t.Fatalf("computePublicEffectiveScope() = %q, %v; want %q", got, err, allowed)
	}
	if got, err := computePublicEffectiveScope(app, "", provider); err != nil || got != allowed {
		t.Fatalf("computePublicEffectiveScope(empty request) = %q, %v; want sole allowed scope %q", got, err, allowed)
	}

	for _, requested := range []string{"api", "full", "api.read", "chatter_api", "openid", "custom.scope"} {
		t.Run(requested, func(t *testing.T) {
			if _, err := computePublicEffectiveScope(app, requested, provider); err == nil {
				t.Fatalf("computePublicEffectiveScope(%q) succeeded, want error", requested)
			}
		})
	}

	secondAllowed := "external.analytics.read"
	multipleAllowed := allowed + " " + secondAllowed
	app.Scopes = &multipleAllowed
	provider[secondAllowed] = true
	if _, err := computePublicEffectiveScope(app, "", provider); err == nil {
		t.Fatal("computePublicEffectiveScope(empty request) succeeded with multiple allowed scopes, want error")
	}
}

type publicScopeProviderStub map[string]bool

func (p publicScopeProviderStub) AllowsPublicOAuthScope(scope string) bool {
	return p[scope]
}

type packPublicScopeProviderStub map[string]map[string]bool

func (p packPublicScopeProviderStub) AllowsPublicOAuthScope(scope string) bool {
	return len(p[scope]) > 0
}

func (p packPublicScopeProviderStub) AllowsPublicOAuthScopeForPack(scope, packName string) bool {
	return p[scope][packName]
}

func TestComputePublicEffectiveScopeRequiresSourcePackScopeOwner(t *testing.T) {
	publicType := OAuthClientTypePublic
	allowed := "external.records.read"
	sourcePack := "crm-pack"
	app := &models.SystemConnectedApp{
		ClientType:     &publicType,
		Scopes:         &allowed,
		SourcePackName: &sourcePack,
	}

	if _, err := computePublicEffectiveScope(app, allowed, publicScopeProviderStub{allowed: true}); err == nil {
		t.Fatal("computePublicEffectiveScope accepted bare public scope provider for source-backed app")
	}

	wrongOwner := packPublicScopeProviderStub{allowed: {"other-pack": true}}
	if _, err := computePublicEffectiveScope(app, allowed, wrongOwner); err == nil {
		t.Fatal("computePublicEffectiveScope accepted scope owned by another pack")
	}
	if _, err := computePublicEffectiveScope(app, "", wrongOwner); err == nil {
		t.Fatal("computePublicEffectiveScope defaulted an empty request to a scope owned by another pack")
	}

	rightOwner := packPublicScopeProviderStub{allowed: {sourcePack: true}}
	if got, err := computePublicEffectiveScope(app, allowed, rightOwner); err != nil || got != allowed {
		t.Fatalf("computePublicEffectiveScope() = %q, %v; want %q", got, err, allowed)
	}
	if got, err := computePublicEffectiveScope(app, "", rightOwner); err != nil || got != allowed {
		t.Fatalf("computePublicEffectiveScope(empty request) = %q, %v; want sole source-owned scope %q", got, err, allowed)
	}
}

func TestConnectedAppAllowsGrantPreservesLegacyConfidentialGrantsWhenUnset(t *testing.T) {
	confidentialType := OAuthClientTypeConfidential
	publicType := OAuthClientTypePublic
	emptyGrants := " \n\t "

	for _, tt := range []struct {
		name string
		app  *models.SystemConnectedApp
	}{
		{
			name: "missing allowed grants",
			app:  &models.SystemConnectedApp{ClientType: &confidentialType},
		},
		{
			name: "empty allowed grants",
			app: &models.SystemConnectedApp{
				ClientType:        &confidentialType,
				AllowedGrantTypes: &emptyGrants,
			},
		},
		{
			name: "legacy default confidential client type",
			app:  &models.SystemConnectedApp{},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			for _, grant := range []string{OAuthGrantAuthorizationCode, OAuthGrantPassword, OAuthGrantClientCredentials} {
				if !connectedAppAllowsGrant(tt.app, grant) {
					t.Fatalf("connectedAppAllowsGrant(%q) = false, want legacy confidential grant allowed", grant)
				}
			}
			if connectedAppAllowsGrant(tt.app, "refresh_token") {
				t.Fatal("connectedAppAllowsGrant(refresh_token) = true, want unsupported legacy grant denied")
			}
		})
	}

	publicApp := &models.SystemConnectedApp{ClientType: &publicType}
	for _, grant := range []string{OAuthGrantAuthorizationCode, OAuthGrantPassword, OAuthGrantClientCredentials} {
		if connectedAppAllowsGrant(publicApp, grant) {
			t.Fatalf("connectedAppAllowsGrant(public, %q) = true with unset grants, want explicit public metadata required", grant)
		}
	}
}

func TestConnectedAppAllowsGrantHonorsExplicitGrantMetadata(t *testing.T) {
	confidentialType := OAuthClientTypeConfidential
	authorizationCodeOnly := OAuthGrantAuthorizationCode
	app := &models.SystemConnectedApp{
		ClientType:        &confidentialType,
		AllowedGrantTypes: &authorizationCodeOnly,
	}

	if !connectedAppAllowsGrant(app, OAuthGrantAuthorizationCode) {
		t.Fatal("connectedAppAllowsGrant(authorization_code) = false, want explicit grant allowed")
	}
	for _, grant := range []string{OAuthGrantPassword, OAuthGrantClientCredentials} {
		if connectedAppAllowsGrant(app, grant) {
			t.Fatalf("connectedAppAllowsGrant(%q) = true, want explicit grant metadata enforced", grant)
		}
	}
}

func TestPKCERequiresS256AndVerifierMatch(t *testing.T) {
	verifier := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
	sum := sha256.Sum256([]byte(verifier))
	challenge := base64.RawURLEncoding.EncodeToString(sum[:])

	if err := validatePKCEChallenge(OAuthCodeChallengeMethodS256, challenge); err != nil {
		t.Fatalf("validatePKCEChallenge() error = %v", err)
	}
	if err := verifyPKCEVerifier(verifier, challenge); err != nil {
		t.Fatalf("verifyPKCEVerifier() error = %v", err)
	}
	if err := validatePKCEChallenge("plain", challenge); err == nil {
		t.Fatal("plain PKCE method succeeded, want error")
	}
	if err := verifyPKCEVerifier(verifier+"x", challenge); err == nil {
		t.Fatal("wrong verifier succeeded, want error")
	}
	if err := verifyPKCEVerifier("short", challenge); err == nil {
		t.Fatal("short verifier succeeded, want error")
	}
}

func TestValidateRegisteredRedirectURIRejectsUnsafeShapes(t *testing.T) {
	redirects := `["https://client.example/callback","http://127.0.0.1:8080/callback"]`
	app := &models.SystemConnectedApp{RedirectURIs: &redirects}

	if err := validateRegisteredRedirectURI(app, "https://client.example/callback"); err != nil {
		t.Fatalf("registered https redirect rejected: %v", err)
	}
	if err := validateRegisteredRedirectURI(app, "http://127.0.0.1:8080/callback"); err != nil {
		t.Fatalf("registered loopback redirect rejected: %v", err)
	}

	for _, redirect := range []string{
		"https://client.example/callback#frag",
		"https://user:pass@client.example/callback",
		"http://client.example/callback",
		"javascript:alert(1)",
		"/relative/callback",
		"https://attacker.example/callback",
		"http://127.0.0.1:9090/callback",
	} {
		t.Run(redirect, func(t *testing.T) {
			if err := validateRegisteredRedirectURI(app, redirect); err == nil {
				t.Fatalf("validateRegisteredRedirectURI(%q) succeeded, want error", redirect)
			}
		})
	}
}

func TestPublicOAuthPKCEEnabledIsExplicitOptIn(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "")
	if PublicOAuthPKCEEnabled() {
		t.Fatal("public PKCE should be disabled without explicit opt-in")
	}
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	if !PublicOAuthPKCEEnabled() {
		t.Fatal("public PKCE should be enabled by true")
	}
}

func TestPublicOAuthCanonicalIssuerRequiresExplicitValidBaseURL(t *testing.T) {
	if _, err := publicOAuthCanonicalIssuerFromLookup(func(string) string { return "" }); err == nil {
		t.Fatal("empty API_BASE_URL produced public issuer, want error")
	}
	if _, err := publicOAuthCanonicalIssuerFromLookup(func(string) string { return "http://client.example" }); err == nil {
		t.Fatal("non-HTTPS public issuer produced issuer, want error")
	}
	got, err := publicOAuthCanonicalIssuerFromLookup(func(string) string { return "https://nexus.example/" })
	if err != nil {
		t.Fatalf("valid issuer rejected: %v", err)
	}
	if got != "https://nexus.example" {
		t.Fatalf("issuer = %q, want trimmed canonical issuer", got)
	}
}

func TestResolveDeploymentResourceIsHostOnlyOrigin(t *testing.T) {
	cases := []struct {
		apiBaseURL string
		want       string
	}{
		{"https://prod.example", "https://prod.example"},
		{"https://prod.example/", "https://prod.example"},
		{"https://api.example:8443", "https://api.example:8443"},
		{"https://api.example/services", "https://api.example"},      // path dropped (not rejected): origin only
		{"https://api.example:8443/x/y", "https://api.example:8443"}, // port kept, path dropped
		{"", ""},                      // unset → empty (binding no-ops in dev)
		{"http://client.example", ""}, // non-loopback http is invalid → empty
	}
	for _, tc := range cases {
		t.Setenv(publicOAuthIssuerEnv, tc.apiBaseURL)
		if got := resolveDeploymentResource(); got != tc.want {
			t.Fatalf("resolveDeploymentResource(API_BASE_URL=%q) = %q, want %q", tc.apiBaseURL, got, tc.want)
		}
	}
}

func TestValidateMCPResourceRequestCanonicalizesOnlyTheDeploymentMCPURL(t *testing.T) {
	t.Setenv(publicOAuthIssuerEnv, "https://Nexus.Example")

	got, err := ValidateMCPResourceRequest("HTTPS://NEXUS.EXAMPLE/mcp", true)
	if err != nil || got != "https://nexus.example/mcp" {
		t.Fatalf("ValidateMCPResourceRequest canonical value = %q, %v", got, err)
	}
	if got, err := ValidateMCPResourceRequest("", false); err != nil || got != "" {
		t.Fatalf("missing resource = %q, %v; want ordinary OAuth flow", got, err)
	}

	for _, raw := range []string{
		"https://nexus.example/mcp/",
		"https://nexus.example/mcp?tenant=other",
		"https://nexus.example/mcp#fragment",
		"https://user@nexus.example/mcp",
		"https://nexus.example/api/mcp",
		"https://other.example/mcp",
		"",
	} {
		if _, err := ValidateMCPResourceRequest(raw, true); err == nil {
			t.Fatalf("ValidateMCPResourceRequest(%q) succeeded", raw)
		} else if oauthErr, ok := OAuthProtocolErrorFrom(err); !ok || oauthErr.Code != "invalid_target" {
			t.Fatalf("ValidateMCPResourceRequest(%q) error = %#v, want invalid_target", raw, err)
		}
	}
}

func TestResourceIsSecurePublicOrigin(t *testing.T) {
	cases := []struct {
		res string
		ok  bool
	}{
		{"https://prod.example.com", true},
		{"https://nexuscrm-new-prod.pingcap.com", true},
		{"https://prod.example.com:8443", true},
		{"http://prod.example.com", false}, // non-https
		{"https://127.0.0.1:3001", false},  // loopback
		{"https://127.0.0.2", false},       // entire 127.0.0.0/8
		{"https://10.0.0.1", false},        // private
		{"https://169.254.1.1", false},     // link-local
		{"https://127.1", false},           // shorthand loopback (no alphabetic TLD)
		{"https://localhost", false},       // localhost
		{"https://internal", false},        // single-label, no TLD
		{"https://[::1]", false},           // IPv6 loopback
		{"https://0.0.0.0", false},         // unspecified
		{"", false},                        // empty
	}
	for _, tc := range cases {
		if got := resourceIsSecurePublicOrigin(tc.res); got != tc.ok {
			t.Fatalf("resourceIsSecurePublicOrigin(%q) = %v, want %v", tc.res, got, tc.ok)
		}
	}
}
