import { createHash } from 'crypto';

/**
 * PKCE工具类
 * 实现RFC 7636 - Proof Key for Code Exchange
 */
export class PKCEUtils {
    /**
     * 支持的代码挑战方法
     */
    static readonly SUPPORTED_METHODS = ['plain', 'S256'] as const;

    /**
     * 验证代码挑战方法是否支持
     */
    static isSupportedMethod(method: string): method is typeof PKCEUtils.SUPPORTED_METHODS[number] {
        return PKCEUtils.SUPPORTED_METHODS.includes(method as any);
    }

    /**
     * 验证代码验证器格式
     * 根据RFC 7636，代码验证器必须是43-128个字符的字符串
     * 只能包含 [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
     */
    static isValidCodeVerifier(codeVerifier: string): boolean {
        if (!codeVerifier || typeof codeVerifier !== 'string') {
            return false;
        }

        // 检查长度
        if (codeVerifier.length < 43 || codeVerifier.length > 128) {
            return false;
        }

        // 检查字符集
        const validChars = /^[A-Za-z0-9\-._~]+$/;
        return validChars.test(codeVerifier);
    }

    /**
     * 验证代码挑战格式
     * 代码挑战的格式取决于使用的方法
     */
    static isValidCodeChallenge(codeChallenge: string, method: string): boolean {
        if (!codeChallenge || typeof codeChallenge !== 'string') {
            return false;
        }

        switch (method) {
            case 'plain':
                // plain方法下，代码挑战就是代码验证器
                return PKCEUtils.isValidCodeVerifier(codeChallenge);

            case 'S256':
                // S256方法下，代码挑战是base64url编码的SHA256哈希
                // 长度应该是43个字符（256位哈希的base64url编码）
                if (codeChallenge.length !== 43) {
                    return false;
                }
                // 检查是否是有效的base64url字符
                const base64urlChars = /^[A-Za-z0-9\-_]+$/;
                return base64urlChars.test(codeChallenge);

            default:
                return false;
        }
    }

    /**
     * 生成代码挑战
     * 根据代码验证器和方法生成代码挑战
     */
    static generateCodeChallenge(codeVerifier: string, method: string): string {
        if (!PKCEUtils.isValidCodeVerifier(codeVerifier)) {
            throw new Error('Invalid code verifier');
        }

        if (!PKCEUtils.isSupportedMethod(method)) {
            throw new Error(`Unsupported code challenge method: ${method}`);
        }

        switch (method) {
            case 'plain':
                return codeVerifier;

            case 'S256':
                const hash = createHash('sha256').update(codeVerifier).digest();
                return hash.toString('base64url');

            default:
                throw new Error(`Unsupported code challenge method: ${method}`);
        }
    }

    /**
     * 验证代码验证器和代码挑战是否匹配
     */
    static verifyCodeChallenge(
        codeVerifier: string,
        codeChallenge: string,
        method: string
    ): boolean {
        try {
            if (!PKCEUtils.isValidCodeVerifier(codeVerifier)) {
                return false;
            }

            if (!PKCEUtils.isValidCodeChallenge(codeChallenge, method)) {
                return false;
            }

            const expectedChallenge = PKCEUtils.generateCodeChallenge(codeVerifier, method);
            return expectedChallenge === codeChallenge;
        } catch {
            return false;
        }
    }

    /**
     * 生成随机的代码验证器
     * 用于测试或客户端SDK
     */
    static generateCodeVerifier(): string {
        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
        const length = 128; // 使用最大长度
        let result = '';

        for (let i = 0; i < length; i++) {
            result += chars.charAt(Math.floor(Math.random() * chars.length));
        }

        return result;
    }

    /**
     * 生成代码挑战对（验证器和挑战）
     * 用于测试或客户端SDK
     */
    static generateCodeChallengePair(method: string = 'S256'): {
        codeVerifier: string;
        codeChallenge: string;
        codeChallengeMethod: string;
    } {
        const codeVerifier = PKCEUtils.generateCodeVerifier();
        const codeChallenge = PKCEUtils.generateCodeChallenge(codeVerifier, method);

        return {
            codeVerifier,
            codeChallenge,
            codeChallengeMethod: method,
        };
    }
} 