package api

import (
	"context"
	"net/url"

	jwt "github.com/golang-jwt/jwt/v5"
	"github.com/supabase/auth/internal/models"
)

type contextKey string

func (c contextKey) String() string {
	return "gotrue api context key " + string(c)
}

const (
	tokenKey                = contextKey("jwt")
	inviteTokenKey          = contextKey("invite_token")
	signatureKey            = contextKey("signature")
	externalProviderTypeKey = contextKey("external_provider_type")
	userKey                 = contextKey("user")
	targetUserKey           = contextKey("target_user")
	factorKey               = contextKey("factor")
	sessionKey              = contextKey("session")
	externalReferrerKey     = contextKey("external_referrer")
	functionHooksKey        = contextKey("function_hooks")
	adminUserKey            = contextKey("admin_user")
	oauthTokenKey           = contextKey("oauth_token") // for OAuth1.0, also known as request token
	oauthVerifierKey        = contextKey("oauth_verifier")
	ssoProviderKey          = contextKey("sso_provider")
	externalHostKey         = contextKey("external_host")
	flowStateKey            = contextKey("flow_state_id")
)

// withToken adds the JWT token to the context.
func withToken(ctx context.Context, token *jwt.Token) context.Context {
	return context.WithValue(ctx, tokenKey, token)
}

// getToken reads the JWT token from the context.
func getToken(ctx context.Context) *jwt.Token {
	obj := ctx.Value(tokenKey)
	if obj == nil {
		return nil
	}

	return obj.(*jwt.Token)
}

func getClaims(ctx context.Context) *AccessTokenClaims {
	token := getToken(ctx)
	if token == nil {
		return nil
	}
	return token.Claims.(*AccessTokenClaims)
}

// withUser adds the user to the context.
func withUser(ctx context.Context, u *models.User) context.Context {
	return context.WithValue(ctx, userKey, u)
}

// withTargetUser adds the target user for linking to the context.
func withTargetUser(ctx context.Context, u *models.User) context.Context {
	return context.WithValue(ctx, targetUserKey, u)
}

// with Factor adds the factor id to the context.
func withFactor(ctx context.Context, f *models.Factor) context.Context {
	return context.WithValue(ctx, factorKey, f)
}

// getUser reads the user from the context.
func getUser(ctx context.Context) *models.User {
	if ctx == nil {
		return nil
	}
	obj := ctx.Value(userKey)
	if obj == nil {
		return nil
	}
	return obj.(*models.User)
}

// getTargetUser reads the user from the context.
func getTargetUser(ctx context.Context) *models.User {
	if ctx == nil {
		return nil
	}
	obj := ctx.Value(targetUserKey)
	if obj == nil {
		return nil
	}
	return obj.(*models.User)
}

// getFactor reads the factor id from the context
func getFactor(ctx context.Context) *models.Factor {
	obj := ctx.Value(factorKey)
	if obj == nil {
		return nil
	}
	return obj.(*models.Factor)
}

// withSession adds the session to the context.
func withSession(ctx context.Context, s *models.Session) context.Context {
	return context.WithValue(ctx, sessionKey, s)
}

// getSession reads the session from the context.
func getSession(ctx context.Context) *models.Session {
	if ctx == nil {
		return nil
	}
	obj := ctx.Value(sessionKey)
	if obj == nil {
		return nil
	}
	return obj.(*models.Session)
}

// withSignature adds the provided request ID to the context.
func withSignature(ctx context.Context, id string) context.Context {
	return context.WithValue(ctx, signatureKey, id)
}

func withInviteToken(ctx context.Context, token string) context.Context {
	return context.WithValue(ctx, inviteTokenKey, token)
}

func withFlowStateID(ctx context.Context, FlowStateID string) context.Context {
	return context.WithValue(ctx, flowStateKey, FlowStateID)
}

func getFlowStateID(ctx context.Context) string {
	obj := ctx.Value(flowStateKey)
	if obj == nil {
		return ""
	}
	return obj.(string)
}

func getInviteToken(ctx context.Context) string {
	obj := ctx.Value(inviteTokenKey)
	if obj == nil {
		return ""
	}

	return obj.(string)
}

// withExternalProviderType adds the provided request ID to the context.
func withExternalProviderType(ctx context.Context, id string) context.Context {
	return context.WithValue(ctx, externalProviderTypeKey, id)
}

// getExternalProviderType reads the request ID from the context.
func getExternalProviderType(ctx context.Context) string {
	obj := ctx.Value(externalProviderTypeKey)
	if obj == nil {
		return ""
	}

	return obj.(string)
}

func withExternalReferrer(ctx context.Context, token string) context.Context {
	return context.WithValue(ctx, externalReferrerKey, token)
}

func getExternalReferrer(ctx context.Context) string {
	obj := ctx.Value(externalReferrerKey)
	if obj == nil {
		return ""
	}

	return obj.(string)
}

// withAdminUser adds the admin user to the context.
func withAdminUser(ctx context.Context, u *models.User) context.Context {
	return context.WithValue(ctx, adminUserKey, u)
}

// getAdminUser reads the admin user from the context.
func getAdminUser(ctx context.Context) *models.User {
	obj := ctx.Value(adminUserKey)
	if obj == nil {
		return nil
	}
	return obj.(*models.User)
}

// withRequestToken adds the request token to the context
func withRequestToken(ctx context.Context, token string) context.Context {
	return context.WithValue(ctx, oauthTokenKey, token)
}

func getRequestToken(ctx context.Context) string {
	obj := ctx.Value(oauthTokenKey)
	if obj == nil {
		return ""
	}
	return obj.(string)
}

func withOAuthVerifier(ctx context.Context, token string) context.Context {
	return context.WithValue(ctx, oauthVerifierKey, token)
}

func getOAuthVerifier(ctx context.Context) string {
	obj := ctx.Value(oauthVerifierKey)
	if obj == nil {
		return ""
	}
	return obj.(string)
}

func withSSOProvider(ctx context.Context, provider *models.SSOProvider) context.Context {
	return context.WithValue(ctx, ssoProviderKey, provider)
}

func getSSOProvider(ctx context.Context) *models.SSOProvider {
	obj := ctx.Value(ssoProviderKey)
	if obj == nil {
		return nil
	}
	return obj.(*models.SSOProvider)
}

func withExternalHost(ctx context.Context, u *url.URL) context.Context {
	return context.WithValue(ctx, externalHostKey, u)
}

func getExternalHost(ctx context.Context) *url.URL {
	obj := ctx.Value(externalHostKey)
	if obj == nil {
		return nil
	}
	return obj.(*url.URL)
}
