import crypto, { KeyLike, Cipheriv, Decipheriv } from 'crypto';
import { CoerceToUint8ArrayInput } from '@alessiofrittoli/crypto-buffer';
import { Readable, Writable, Transform } from 'stream';

/**
 * Cipher types.
 *
 */
declare namespace Cph {
    /** Cipher CBC algorithm types. */
    type CBCTypes = 'aes-128-cbc' | 'aes-192-cbc' | 'aes-256-cbc';
    /** Cipher supported algorithm types. */
    type AesAlgorithm = typeof Cipher.ALGORITHM[keyof typeof Cipher.ALGORITHM];
    /**
     * Common Cipher options.
     * @template T The AES algorithm type.
     */
    interface Options<T extends Cph.AesAlgorithm = Cph.AesAlgorithm> {
        /** The Cipher algorithm to use. Default: `aes-256-gcm` | `aes-256-cbc`. */
        algorithm?: T;
        /** The salt length. Minimum: `16`, Maximum: `64`. Default: `32`. */
        salt?: number;
        /** The `IV` length. Minimum: `8`, Maximum: `32`. Default: `16`. */
        iv?: number;
        /** The `authTag` length. Minimum: `4`, Maximum: `16`. Default: `16`. */
        authTag?: number;
        /** Custom `Additional Authenticated Data`. */
        aad?: CoerceToUint8ArrayInput;
        /** The `AAD` length. Minimum: `16`, Maximum: `128`. Default: `32`. */
        aadLength?: number;
    }
    interface NewKeyIVOptions<T extends Cph.AesAlgorithm = Cph.AesAlgorithm> extends Cph.Options<T> {
        secret?: CoerceToUint8ArrayInput;
    }
    /**
     * Resolved Common options.
     */
    interface ResolvedOptions<T extends Cph.AesAlgorithm = Cph.AesAlgorithm> extends Required<Omit<Cph.Options<T>, 'aad'>> {
        /** The symmetric key length. */
        length: number;
        /** The Additional Authentication Data. */
        aad?: Uint8Array;
    }
    /** The RSA Private Key. */
    type PrivateKey = KeyLike | {
        key: KeyLike;
        passphrase?: string;
    };
    namespace Stream {
        /**
         * Options for encrypting a stream.
         *
         */
        interface EncryptOptions extends Pick<Cph.Options<Cph.CBCTypes>, 'algorithm'> {
            /** The `Readable` Stream from where raw data to encrypt is read. */
            input: Readable;
            /** The `Writable` Stream where encrypted data is written. */
            output: Writable;
        }
        /**
         * Options for decrypting a stream.
         *
         */
        type DecryptOptions = Cph.Stream.EncryptOptions;
        /**
         * Resolved Stream Encrypt options.
         *
         */
        type EncryptResolvedOptions = (Cph.ResolvedOptions & Required<Cph.Stream.EncryptOptions>);
        /**
         * Resolved Stream Decrypt options.
         *
         */
        type DecryptResolvedOptions = (Cph.ResolvedOptions & Required<Cph.Stream.DecryptOptions>);
        /**
         * INTERNAL USE ONLY
         */
        interface CipherOptions {
            cipher: Cipheriv;
            encryptedKey?: Buffer;
            input: Readable;
            output: Writable;
        }
        /**
         * INTERNAL USE ONLY
         */
        interface DecipherOptions {
            decipher: Decipheriv;
            input: Readable;
            output: Writable;
        }
        type ExtractedKeyLength = [KeyLength: number, input: Readable | Transform];
        type ExtractedBytes = [DataRead: Buffer, input: Transform];
    }
    namespace Decrypt {
        interface ExtractKeyOptions {
            privateKey: Cph.PrivateKey;
            EncryptedKeyIV: Buffer;
            keyLength: number;
        }
    }
}

declare class Cipher {
    /**
     * Cipher default `salt` lengths.
     *
     */
    static readonly SALT_LENGTH: {
        readonly min: 16;
        readonly max: 64;
        readonly default: 32;
    };
    /**
     * Cipher default `IV` lengths.
     *
     */
    static readonly IV_LENGTH: {
        readonly min: 8;
        readonly max: 32;
        readonly default: 16;
    };
    /**
     * Cipher default `Auth Tag` lengths.
     *
     */
    static readonly AUTH_TAG_LENGTH: {
        readonly min: 4;
        readonly max: 16;
        readonly default: 16;
    };
    /**
     * Cipher default `Additional Authenticated Data` lengths.
     *
     */
    static readonly AAD_LENGTH: {
        readonly min: 16;
        readonly max: 128;
        readonly default: 32;
    };
    /**
     * Cipher algorithm name.
     *
     */
    static readonly ALGORITHM: {
        /** AES 128 bit Cipher Block Chaining. */
        readonly AES_128_CBC: "aes-128-cbc";
        /** AES 192 bit Cipher Block Chaining. */
        readonly AES_192_CBC: "aes-192-cbc";
        /** AES 256 bit Cipher Block Chaining. */
        readonly AES_256_CBC: "aes-256-cbc";
        /** AES 128 bit AEAD Counter with CBC-MAC. */
        readonly AES_128_CCM: "aes-128-ccm";
        /** AES 192 bit AEAD Counter with CBC-MAC. */
        readonly AES_192_CCM: "aes-192-ccm";
        /** AES 256 bit AEAD Counter with CBC-MAC. */
        readonly AES_256_CCM: "aes-256-ccm";
        /** AES 128 bit AEAD Galois/Counter Mode. */
        readonly AES_128_GCM: "aes-128-gcm";
        /** AES 192 bit AEAD Galois/Counter Mode. */
        readonly AES_192_GCM: "aes-192-gcm";
        /** AES 256 bit AEAD Galois/Counter Mode. */
        readonly AES_256_GCM: "aes-256-gcm";
        /** AES 128 bit AEAD Offset Codebook Mode. */
        readonly AES_128_OCB: "aes-128-ocb";
        /** AES 192 bit AEAD Offset Codebook Mode. */
        readonly AES_192_OCB: "aes-192-ocb";
        /** AES 256 bit AEAD Offset Codebook Mode. */
        readonly AES_256_OCB: "aes-256-ocb";
        /** AES 256 bit AEAD. */
        readonly CHACHA_20_POLY: "chacha20-poly1305";
    };
    /**
     * Default AES algorithms used based on functionality.
     *
     */
    static readonly DEFAULT_ALGORITHM: {
        readonly buffer: "aes-256-gcm";
        readonly stream: "aes-256-cbc";
    };
    /**
     * Supported AES algorithms.
     *
     */
    static readonly ALGORITHMS: Cph.AesAlgorithm[];
    /**
     * Encrypt in-memory data buffer.
     *
     * ⚠️ This is not suitable for large data encryption. Use {@link Cipher.stream.Encrypt()} or {@link Cipher.stream.HybridEncrypt()} methods for large data encryption.
     *
     * @param	data	The data to encrypt.
     * @param	secret	The secret key used to encrypt the `data`.
     * @param	options	(Optional) Additional options.
     * @returns	The encrypted result Buffer.
     */
    static Encrypt(data: CoerceToUint8ArrayInput, secret: CoerceToUint8ArrayInput, options?: Cph.Options): Buffer<ArrayBuffer>;
    /**
     * Decrypt in-memory data buffer.
     *
     * ⚠️ This is not suitable for large data decryption. Use {@link Cipher.stream.Decrypt()} or {@link Cipher.stream.HybridDecrypt()} methods for large data decryption.
     *
     * @param	data	The data to decrypt.
     * @param	secret	The secret key used to decrypt the `data`.
     * @param	options	(Optional) Additional options. Must be the same used while encrypting data.
     * @returns	The decrypted data Buffer.
     */
    static Decrypt(data: CoerceToUint8ArrayInput, secret: CoerceToUint8ArrayInput, options?: Cph.Options): Buffer<ArrayBuffer>;
    /**
     * Encrypt in-memory data using hybrid encryption.
     *
     * ⚠️ This is not suitable for large data encryption. Use {@link Cipher.stream.HybridEncrypt()} method for large data encryption.
     *
     * @param	data	The data to encrypt.
     * @param	key		The RSA Public Key.
     * @param	options	(Optional) Additional options.
     *
     * @returns	The encrypted result Buffer.
     */
    static HybridEncrypt(data: CoerceToUint8ArrayInput, key: crypto.KeyLike, options?: Cph.Options): Buffer<ArrayBuffer>;
    /**
     * Decrypt in-memory data using hybrid decryption.
     *
     * ⚠️ This is not suitable for large data decryption. Use {@link Cipher.stream.HybridDecrypt()} method for large data decryption.
     *
     * @param	data	The encrypted data to decrypt.
     * @param	key		The RSA Private Key.
     * @param	options	(Optional) Additional options.
     *
     * @returns	The encrypted result Buffer.
     */
    static HybridDecrypt(data: CoerceToUint8ArrayInput, key: Cph.PrivateKey, options?: Cph.Options): Buffer<ArrayBuffer>;
    /**
     * Cipher stream based functions.
     *
     */
    static stream: {
        /**
         * Encrypt stream data.
         *
         * @param	secret	The secret key used to encrypt the data.
         * @param	options An object defining required options. See {@link Cph.Stream.EncryptOptions} for more info.
         *
         * @returns A new Promise that resolves `void` once stream encryption is completed.
         */
        Encrypt(secret: CoerceToUint8ArrayInput, options: Cph.Stream.EncryptOptions): Promise<void>;
        /**
         * Decrypt stream data.
         *
         * @param	secret	The secret key used to decrypt the data.
         * @param	options	An object defining required options. See {@link Cph.Stream.DecryptOptions} for more info.
         *
         * @returns	A new Promise that resolves `void` once stream decryption is completed.
         */
        Decrypt(secret: CoerceToUint8ArrayInput, options: Cph.Stream.DecryptOptions): Promise<void>;
        /**
         * Encrypt stream data using hybrid encryption.
         *
         * @param	key		The RSA Public Key.
         * @param	options	An object defining required options. See {@link Cph.Stream.EncryptOptions} for more info.
         *
         * @returns	A new Promise that resolves `void` once stream encryption is completed.
         */
        HybridEncrypt(key: crypto.KeyLike, options: Cph.Stream.EncryptOptions): Promise<void>;
        /**
         * Decrypt stream data using hybrid decryption.
         *
         * @param	key		The private key.
         * @param	options	An object defining required options. See {@link Cph.Stream.DecryptOptions} for more info.
         *
         * @returns	A new Promise that resolves `void` once stream decryption is completed.
         */
        HybridDecrypt(key: Cph.PrivateKey, options: Cph.Stream.DecryptOptions): Promise<void>;
        /**
         * Handle pipe flow to encrypt a Stream.
         *
         * @param	options Required parameters.
         * @returns A new Promise that resolves `void` once stream is completed.
         */
        Cipher(options: Cph.Stream.CipherOptions): Promise<void>;
        /**
         * Handle pipe flow to decrypt a Stream.
         *
         * @param	options Required parameters.
         * @returns A new Promise that resolves `void` once stream is completed.
         */
        Decipher(options: Cph.Stream.DecipherOptions): Promise<void>;
    };
    private static _stream;
    /**
     * Decrypt Key and Initialization Vector.
     *
     * @param	options An object containing required data.
     * @returns	An object containing decrpyted `Key` and `IV`.
     */
    private static DecryptKeyIV;
    private static GetPrivateKey;
    /**
     * Generates a `Scrypt` Symmetric Key and the Initialization Vector with the given options.
     *
     * @param options (Optional) Additional options.
     *
     * @returns An object with the generated `Key`, `IV`, `salt` and resolved `options`.
     */
    private static NewKeyIV;
    /**
     * Resolves the given `options` with `Cipher` defaults and constraints.
     *
     * @param options (Optional) Additional options.
     * @returns The given `options` with `Cipher` defaults and constraints.
     */
    private static ResolveOptions;
    /**
     * Get the Initialization Vector length based on the given algorithm.
     *
     * @param	algorithm The algorithm in use.
     * @param	options (Optional) Additional options.
     * @returns	The Initialization Vector length based on the given algorithm
     */
    private static GetIVLength;
    /**
     * Ensure correct key length based on the given algorithm.
     *
     * @param	algorithm The AES algorithm name.
     * @returns	An object with validated `algorithm` and `keyLength`.
     */
    private static GetKeyLength;
    /**
     * Check if the given algorithm is a Cipher AES-GCM algorithm.
     *
     * @param algorithm The AES Algorithm to check.
     * @returns `true` if the given algorithm is a Cipher AES-GCM algorithm. `false` otherwise.
     */
    static IsGCM(algorithm: Cph.AesAlgorithm): algorithm is crypto.CipherGCMTypes;
    /**
     * Check if the given algorithm is a Cipher AES-CCM algorithm.
     *
     * @param algorithm The AES Algorithm to check.
     * @returns `true` if the given algorithm is a Cipher AES-CCM algorithm. `false` otherwise.
     */
    static IsCCM(algorithm: Cph.AesAlgorithm): algorithm is crypto.CipherCCMTypes;
    /**
     * Check if the given algorithm is a Cipher chacha20-poly1305 algorithm.
     *
     * @param algorithm The AES Algorithm to check.
     * @returns `true` if the given algorithm is a Cipher chacha20-poly1305 algorithm. `false` otherwise.
     */
    static IsChacha20Poly(algorithm: Cph.AesAlgorithm): algorithm is crypto.CipherChaCha20Poly1305Types;
    /**
     * Check if the given algorithm is a Cipher AES-OCB algorithm.
     *
     * @param algorithm The AES Algorithm to check.
     * @returns `true` if the given algorithm is a Cipher AES-OCB algorithm. `false` otherwise.
     */
    static IsOCB(algorithm: Cph.AesAlgorithm): algorithm is crypto.CipherOCBTypes;
    /**
     * Check if the given algorithm is a Cipher AES-CBC algorithm.
     *
     * @param algorithm The AES Algorithm to check.
     * @returns `true` if the given algorithm is a Cipher AES-CBC algorithm. `false` otherwise.
     */
    static IsCBC(algorithm: Cph.AesAlgorithm): algorithm is Cph.CBCTypes;
}

export { Cipher as C, Cph };
