import User from '#models/user';
import BackupCode from '#models/backup_code';
import hash from '@adonisjs/core/services/hash';

export interface ISecureRandom {
    CHAR_UPPER: string;
    CHAR_LOWER: string;
    CHAR_DIGITS: string;
    CHAR_SYMBOLS: string;
    CHAR_ALPHANUMERIC: string;
    CHAR_HUMAN_READABLE: string;

    /**
     * Generate a random string of specified length.
     * @param int $length The length of the generated string
     * @param string $characters An optional list of characters to use if no character list is
     * 							specified all valid base64 characters are used.
     * @return string
     * @since 8.0.0
     */
    generate(length: number, characters?: string): string;
}

export class SecureRandom implements ISecureRandom {
    CHAR_UPPER: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    CHAR_LOWER: string = 'abcdefghijklmnopqrstuvwxyz';
    CHAR_DIGITS: string = '0123456789';
    CHAR_SYMBOLS: string = '!"#$%&\\\'()*+,-./:;<=>?@[]^_`{|}~';
    CHAR_ALPHANUMERIC: string = this.CHAR_UPPER + this.CHAR_LOWER + this.CHAR_DIGITS;
    CHAR_HUMAN_READABLE: string = 'abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789';

    public generate(length: number, characters: string = this.CHAR_ALPHANUMERIC): string {
        if (length <= 0) {
            throw new Error('Invalid length specified: ' + length + ' must be bigger than 0');
        }
        const maxCharIndex: number = characters.length - 1;
        let randomString: string = '';
        while (length > 0) {
            const randomNumber: number = Math.floor(Math.random() * (maxCharIndex + 1));
            randomString += characters[randomNumber];
            length--;
        }
        return randomString;
    }
}

class BackupCodeStorage {
    private static CODE_LENGTH: number = 16;
    // private mapper: BackupCodeMapper;
    // private hasher: IHasher;
    private random: ISecureRandom;
    // private eventDispatcher: IEventDispatcher;

    // constructor(mapper: BackupCodeMapper, random: ISecureRandom, hasher: IHasher, eventDispatcher: IEventDispatcher) {
    //     this.mapper = mapper;
    //     this.hasher = hasher;
    //     this.random = random;
    //     this.eventDispatcher = eventDispatcher;
    // }
    constructor(random: ISecureRandom) {
        // this.mapper = mapper;
        // this.hasher = hasher;
        this.random = random;
        // this.eventDispatcher = eventDispatcher;
    }

    public async createCodes(user: User, number: number = 10): Promise<string[]> {
        let results: string[] = [];
        // this.mapper.deleteCodes(user);
        await BackupCode.deleteCodes(user);
        // user.twoFactorRecoveryCodes = [""];

        // const uid = user.getUID();
        for (let i = 1; i <= Math.min(number, 20); i++) {
            const code = this.random.generate(BackupCodeStorage.CODE_LENGTH, this.random.CHAR_HUMAN_READABLE);
            // const code = crypto
            //     .randomBytes(Math.ceil(BackupCodeStorage.CODE_LENGTH / 2))
            //     .toString('hex')
            //     .slice(0, BackupCodeStorage.CODE_LENGTH);
            const dbCode = new BackupCode();
            // dbCode.setUserId(uid);
          
            // dbCode.setCode(this.hasher.hash(code));
            dbCode.code = await hash.make(code);
            // dbCode.setUsed(0);
            dbCode.used = false;
            // this.mapper.insert(dbCode);
            // await dbCode.save();
            await dbCode.related('user').associate(user); // speichert schon ab
            results.push(code);
        }
        // this.eventDispatcher.dispatchTyped(new CodesGenerated(user));
        return results;
    }

    public async hasBackupCodes(user: User): Promise<boolean> {
        const codes = await user.getBackupCodes();
        return codes.length > 0;
    }

    public async getBackupCodesState(user: User) {
        // const codes = this.mapper.getBackupCodes(user);
        // const codes = await user.related('backupcodes').query().exec();
        const codes: BackupCode[] = await user.getBackupCodes();
        const total = codes.length;
        let used: number = 0;
        codes.forEach((code) => {
            if (code.used === true) {
                used++;
            }
        });
        return {
            enabled: total > 0,
            total: total,
            used: used,
        };
    }

    // public validateCode(user: User, code: string): boolean {
    //     const dbCodes = await user.getBackupCodes();
    //     for (const dbCode of dbCodes) {
    //         if (parseInt(dbCode.getUsed()) === 0 && this.hasher.verify(code, dbCode.getCode())) {
    //             dbCode.setUsed(1);
    //             this.mapper.update(dbCode);
    //             return true;
    //         }
    //     }
    //     return false;
    // }

    // public deleteCodes(user: User): void {
    //     this.mapper.deleteCodes(user);
    // }
}

export default BackupCodeStorage;
