// src/lib/auth/providers/clerk.ts
import { BaseAuthProvider } from "./BaseAuthProvider.js";
import { AuthError } from "../errors.js";
import { logger } from "../../utils/logger.js";
import { createProxyFetch } from "../../proxy/proxyFetch.js";
import * as jose from "jose";
/**
 * Clerk Authentication Provider
 *
 * Supports Clerk's session-based and JWT authentication.
 * Can validate both JWT tokens and session tokens via Clerk API.
 *
 * Features:
 * - JWT validation using Clerk's JWKS
 * - Session token validation via Clerk API
 * - User profile fetching
 * - Organization support for multi-tenant apps
 *
 * @example
 * ```typescript
 * const clerk = new ClerkProvider({
 *   type: "clerk",
 *   publishableKey: "pk_test_...",
 *   secretKey: "sk_test_..."
 * });
 *
 * const result = await clerk.authenticateToken(sessionToken);
 * if (result.valid) {
 *   console.log("Authenticated user:", result.user);
 * }
 * ```
 */
export class ClerkProvider extends BaseAuthProvider {
    type = "clerk";
    secretKey;
    jwtKey;
    publishableKey;
    jwks = null;
    localKey = null;
    constructor(config) {
        super(config);
        if (!config.secretKey) {
            throw AuthError.create("CONFIGURATION_ERROR", "Clerk secretKey is required", { details: { missingFields: ["secretKey"] } });
        }
        this.secretKey = config.secretKey;
        this.jwtKey = config.jwtKey;
        this.publishableKey = config.publishableKey;
    }
    /**
     * Initialize Clerk JWKS
     */
    async initialize() {
        // Clerk JWKS endpoint (v1 API)
        const jwksUrl = new URL("https://api.clerk.com/v1/jwks");
        this.jwks = jose.createRemoteJWKSet(jwksUrl);
        logger.debug("Clerk provider initialized");
    }
    /**
     * Validate Clerk session token or JWT
     */
    async authenticateToken(token, _context) {
        // First try JWT validation (tokens with dots)
        if (token.includes(".") && token.split(".").length === 3) {
            return this.validateJWT(token);
        }
        // Otherwise treat as session token
        return this.validateSessionToken(token);
    }
    /**
     * Validate JWT using local jwtKey (if configured) or JWKS
     */
    async validateJWT(token) {
        try {
            let payload;
            if (this.jwtKey) {
                // Use locally provided JWT key for verification
                if (!this.localKey) {
                    this.localKey = new TextEncoder().encode(this.jwtKey);
                }
                ({ payload } = await jose.jwtVerify(token, this.localKey));
            }
            else {
                // Fall back to Clerk JWKS endpoint
                if (!this.jwks) {
                    await this.initialize();
                }
                if (!this.jwks) {
                    return {
                        valid: false,
                        error: "Clerk JWKS not initialized",
                    };
                }
                ({ payload } = await jose.jwtVerify(token, this.jwks));
            }
            // Validate azp (authorized party) claim if publishableKey is configured
            if (this.publishableKey && payload.azp) {
                if (payload.azp !== this.publishableKey) {
                    return {
                        valid: false,
                        error: `Invalid authorized party: ${payload.azp}. Expected: ${this.publishableKey}`,
                    };
                }
            }
            const user = {
                id: payload.sub,
                email: payload.email,
                name: payload.name,
                picture: payload.picture,
                emailVerified: payload.email_verified,
                roles: payload["https://clerk.dev/roles"] || [],
                permissions: payload["https://clerk.dev/permissions"] || [],
                organizationId: payload.org_id,
                metadata: {
                    azp: payload.azp,
                    sid: payload.sid,
                },
            };
            return {
                valid: true,
                payload: payload,
                user,
                expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
                tokenType: "jwt",
            };
        }
        catch (error) {
            return {
                valid: false,
                error: error instanceof Error ? error.message : String(error),
            };
        }
    }
    /**
     * Validate session token via Clerk API
     */
    async validateSessionToken(token) {
        try {
            const proxyFetch = createProxyFetch();
            const response = await proxyFetch("https://api.clerk.com/v1/sessions/verify", {
                method: "POST",
                headers: {
                    Authorization: `Bearer ${this.secretKey}`,
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ token }),
            });
            if (!response.ok) {
                const error = (await response.json());
                return {
                    valid: false,
                    error: error.errors?.[0]?.message || "Session validation failed",
                };
            }
            const session = (await response.json());
            const userData = session.user;
            const emailAddresses = userData?.email_addresses;
            const user = {
                id: session.user_id,
                email: emailAddresses?.[0]?.email_address,
                name: userData?.first_name
                    ? `${userData.first_name} ${userData.last_name || ""}`.trim()
                    : undefined,
                picture: userData?.image_url,
                roles: userData?.public_metadata
                    ?.roles || [],
                permissions: userData?.public_metadata
                    ?.permissions || [],
                organizationId: session.active_organization_id,
            };
            return {
                valid: true,
                payload: session,
                user,
                expiresAt: session.expire_at
                    ? new Date(session.expire_at)
                    : undefined,
                tokenType: "session",
            };
        }
        catch (error) {
            return {
                valid: false,
                error: error instanceof Error ? error.message : String(error),
            };
        }
    }
    /**
     * Get user by ID from Clerk API
     */
    async getUser(userId) {
        try {
            const proxyFetch = createProxyFetch();
            const response = await proxyFetch(`https://api.clerk.com/v1/users/${userId}`, {
                headers: {
                    Authorization: `Bearer ${this.secretKey}`,
                },
            });
            if (!response.ok) {
                if (response.status === 404) {
                    return null;
                }
                throw AuthError.create("PROVIDER_ERROR", `Clerk API returned ${response.status}`, { details: { statusCode: response.status } });
            }
            const data = (await response.json());
            const emailAddresses = data.email_addresses;
            return {
                id: data.id,
                email: emailAddresses?.[0]?.email_address,
                name: data.first_name
                    ? `${data.first_name} ${data.last_name || ""}`.trim()
                    : undefined,
                picture: data.image_url,
                emailVerified: emailAddresses?.[0]?.verification?.status === "verified",
                roles: data.public_metadata
                    ?.roles || [],
                permissions: data.public_metadata
                    ?.permissions || [],
                createdAt: data.created_at
                    ? new Date(data.created_at)
                    : undefined,
                lastLoginAt: data.last_sign_in_at
                    ? new Date(data.last_sign_in_at)
                    : undefined,
                metadata: data.private_metadata,
            };
        }
        catch (error) {
            logger.error("Failed to fetch Clerk user:", error);
            if (error &&
                typeof error === "object" &&
                "code" in error &&
                typeof error.code === "string") {
                throw error;
            }
            return null;
        }
    }
    /**
     * Get user by email from Clerk API
     */
    async getUserByEmail(email) {
        try {
            const proxyFetch = createProxyFetch();
            const response = await proxyFetch(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}`, {
                headers: {
                    Authorization: `Bearer ${this.secretKey}`,
                },
            });
            if (!response.ok) {
                throw AuthError.create("PROVIDER_ERROR", `Clerk API returned ${response.status}`, { details: { statusCode: response.status } });
            }
            const users = (await response.json());
            if (users.length === 0) {
                return null;
            }
            const data = users[0];
            const emailAddresses = data.email_addresses;
            return {
                id: data.id,
                email: emailAddresses?.[0]?.email_address,
                name: data.first_name
                    ? `${data.first_name} ${data.last_name || ""}`.trim()
                    : undefined,
                picture: data.image_url,
                emailVerified: emailAddresses?.[0]?.verification?.status === "verified",
                roles: data.public_metadata
                    ?.roles || [],
                permissions: data.public_metadata
                    ?.permissions || [],
                createdAt: data.created_at
                    ? new Date(data.created_at)
                    : undefined,
                lastLoginAt: data.last_sign_in_at
                    ? new Date(data.last_sign_in_at)
                    : undefined,
                metadata: data.private_metadata,
            };
        }
        catch (error) {
            logger.error("Failed to fetch Clerk user by email:", error);
            if (error &&
                typeof error === "object" &&
                "code" in error &&
                typeof error.code === "string") {
                throw error;
            }
            return null;
        }
    }
    /**
     * Health check
     */
    async healthCheck() {
        try {
            const proxyFetch = createProxyFetch();
            // Use a lightweight endpoint to check connectivity
            const response = await proxyFetch("https://api.clerk.com/v1/organizations?limit=1", {
                headers: {
                    Authorization: `Bearer ${this.secretKey}`,
                },
            });
            return {
                healthy: response.ok,
                providerConnected: response.ok,
                sessionStorageHealthy: true,
            };
        }
        catch (error) {
            return {
                healthy: false,
                providerConnected: false,
                sessionStorageHealthy: true,
                error: error instanceof Error ? error.message : String(error),
            };
        }
    }
}
//# sourceMappingURL=clerk.js.map