// const bcrypt = require("bcrypt");
import bcrypt from 'bcryptjs';
// import bcrypt from 'bcrypt/bcrypt.js'
import { HashDriverContract, ManagerDriverFactory } from '@adonisjs/core/types/hash';
import { EnumValidator, RangeValidator, PhcFormatter } from './helpers.js';

/**
 * Config accepted by the hash driver
 */
export type PbkdfConfig = {
    rounds: number;
    saltSize?: number;
    version?: number;
};

const saltRounds = 10;
export class LaravelDriver implements HashDriverContract {
    private config: PbkdfConfig;
    // private binding;
    private phcFormatter = new PhcFormatter();

    constructor(config: {}) {
        this.config = {
            rounds: 10,
            saltSize: 16,
            version: 98,
            ...config,
        };
    }

    public async make(value: string) {
        const hashedValue = bcrypt.hashSync(value, saltRounds);
        return hashedValue;
    }

    public async verify(hashedValue: string, plainValue: string) {
        let newHash: string;
        if (hashedValue.includes('$2y$10$')) {
            newHash = hashedValue.replace('$2y$10$', '$2a$10$');
        } else {
            newHash = hashedValue;
        }
        return await bcrypt.compareSync(plainValue, newHash);
    }

    /**
     * Check if the value is a valid hash. This method just checks
     * for the formatting of the hash.
     *
     * ```ts
     * bcrypt.isValidHash('hello world') // false
     * bcrypt.isValidHash('$bcrypt$v=98$r=10$Jtxi46WJ26OQ0khsYLLlnw$knXGfuRFsSjXdj88JydPOnUIglvm1S8')
     * ```
     */
    public isValidHash(value: string) {
        try {
            this.validatePhcString(value);
            return true;
        } catch {
            return false;
        }
    }

    public needsReHash(value: string): boolean {
        if (value.startsWith('$2b') || value.startsWith('$2a')) {
            return true;
        }
        const phcNode = this.phcFormatter.deserialize(value);
        if (phcNode.id !== 'bcrypt') {
            return true;
        }
        if (phcNode.version !== this.config.version) {
            return true;
        }
        if (!phcNode.params) {
            return true;
        }
        if (phcNode.params.r !== this.config.rounds) {
            return true;
        }
        return false;
    }

    /**
     * Validate phc hash string
     */
    private validatePhcString(phcString: string) {
        const phcNode = this.phcFormatter.deserialize(phcString);
        if (!phcNode.version) {
            phcNode.version = 97;
        }
        if (phcNode.id !== 'bcrypt') {
            throw new TypeError(`Invalid "id" found in the phc string`);
        }
        if (!phcNode.params) {
            throw new TypeError(`No "params" found in the phc string`);
        }
        if (!phcNode.salt) {
            throw new TypeError(`No "salt" found in the phc string`);
        }
        if (!phcNode.hash) {
            throw new TypeError(`No "hash" found in the phc string`);
        }
        if (!phcNode.hash.byteLength) {
            throw new TypeError(`No "hash" found in the phc string`);
        }
        RangeValidator.validate('salt.byteLength', phcNode.salt.byteLength, [8, 1024]);
        EnumValidator.validate('version', phcNode.version, [97, 98]);
        RangeValidator.validate('r', phcNode.params.r, [4, 31]);
        return {
            id: phcNode.id,
            version: phcNode.version,
            hash: phcNode.hash,
            salt: phcNode.salt,
            params: {
                r: phcNode.params.r,
            },
        };
    }

    /**
     * Dynamically importing underlying binding
     */
    // private async importBinding() {
    //     if (this.binding) {
    //         return this.binding;
    //     }
    //     this.binding = await import('bcrypt');
    //     return this.binding;
    // }
}

/**
 * Factory function to reference the driver
 * inside the config file.
 */
export function laravelDriver(config: PbkdfConfig): ManagerDriverFactory {
    return () => {
        return new LaravelDriver(config);
    };
}
