import { DateTime } from 'luxon';
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid';
import { column, manyToMany, hasMany, SnakeCaseNamingStrategy, computed, beforeFetch, beforeFind } from '@adonisjs/lucid/orm';
import hash from '@adonisjs/core/services/hash';
import Role from './role.js';
import db from '@adonisjs/lucid/services/db';
import config from '@adonisjs/core/services/config';
import Dataset from './dataset.js';
import BaseModel from './base_model.js';
// import Encryption from '@ioc:Adonis/Core/Encryption';
import encryption from '@adonisjs/core/services/encryption';
import { TotpState } from '#contracts/enums';
import type { ManyToMany } from '@adonisjs/lucid/types/relations';
import type { HasMany } from '@adonisjs/lucid/types/relations';
import { compose } from '@adonisjs/core/helpers';
import BackupCode from './backup_code.js';

const AuthFinder = withAuthFinder(() => hash.use('laravel'), {
    uids: ['email'],
    passwordColumnName: 'password',
});

// import TotpSecret from './TotpSecret';

// export default interface IUser {
// 	id: number;
// 	login: string;
// 	email: string;
// 	// password: string;
// 	// createdAt: DateTime;
// 	// updatedAt: DateTime;
// 	// async (user): Promise<void>;
// }

// const permissionTable = config.get('rolePermission.permission_table', 'permissions');
// const rolePermissionTable = config.get('rolePermission.role_permission_table', 'role_has_permissions');

// const roleTable = config.get('rolePermission.role_table', 'roles');
// const userRoleTable = config.get('rolePermission.user_role_table', 'link_accounts_roles');

export default class User extends compose(BaseModel, AuthFinder) {
    // export default class User extends BaseModel {
    public static namingStrategy = new SnakeCaseNamingStrategy();
    public static table = 'accounts';

    @column({ isPrimary: true })
    public id: number;

    @column()
    public login: string;

    @column()
    public firstName: string;

    @column()
    public lastName: string;

    @column()
    public email: string;

    @column({ serializeAs: null })
    public password: string;

    @column.dateTime({ autoCreate: true })
    public createdAt: DateTime;

    @column.dateTime({ autoCreate: true, autoUpdate: true })
    public updatedAt: DateTime;

    // serializeAs: null removes the model properties from the serialized output.
    @column({
        serializeAs: null,
        consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '{}') : null),
        prepare: (value: string) => encryption.encrypt(JSON.stringify(value)),
    })
    public twoFactorSecret?: string | null;

    // serializeAs: null removes the model properties from the serialized output.
    @column({
        serializeAs: null,
        consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '[]') : []),
        prepare: (value: string[]) => encryption.encrypt(JSON.stringify(value)),
    })
    public twoFactorRecoveryCodes?: string[] | null;

    @column({})
    public state: number;

    @column({})
    public avatar: string;

    // @hasOne(() => TotpSecret, {
    //     foreignKey: 'user_id',
    // })
    // public totp_secret: HasOne<typeof TotpSecret>;

    // @beforeSave()
    // public static async hashPassword(user: User) {
    //     if (user.$dirty.password) {
    //         user.password = await hash.use('laravel').make(user.password);
    //     }
    // }

    public get isTwoFactorEnabled(): boolean {
        return Boolean(this?.twoFactorSecret && this.state == TotpState.STATE_ENABLED);
        // return Boolean(this.totp_secret?.twoFactorSecret);
    }

  
    @manyToMany(() => Role, {
        pivotForeignKey: 'account_id',
        pivotRelatedForeignKey: 'role_id',
        pivotTable: 'link_accounts_roles',
    })
    public roles: ManyToMany<typeof Role>;

    @hasMany(() => Dataset, {
        foreignKey: 'account_id',
    })
    public datasets: HasMany<typeof Dataset>;

    @hasMany(() => BackupCode, {
        foreignKey: 'user_id',
    })
    public backupcodes: HasMany<typeof BackupCode>;

    @computed({
        serializeAs: 'is_admin',
    })
    public get isAdmin(): boolean {
        const roles = this.roles;
        const isAdmin = roles?.map((role: Role) => role.name).includes('administrator');
        return isAdmin;
    }

    // public toJSON() {
    //     return {
    //         ...super.toJSON(),
    //         roles: []
    //     };
    // }
    @beforeFind()
    @beforeFetch()
    public static preloadRoles(user: User) {
      user.preload('roles')
    }

    public async getBackupCodes(this: User): Promise<BackupCode[]> {
        const test = await this.related('backupcodes').query();
        // return test.map((role) => role.code);
        return test;
    }

    // https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289
    public async getRoles(this: User): Promise<string[]> {
        const test = await this.related('roles').query();
        return test.map((role) => role.name);
    }

    public async can(permissionNames: Array<string>): Promise<boolean> {
        // const permissions = await this.getPermissions()
        // return Acl.check(expression, operand => _.includes(permissions, operand))
        const hasPermission = await this.checkHasPermissions(this, permissionNames);
        return hasPermission;
    }

    private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
        const permissionTable = config.get('rolePermission.permission_table', 'permissions');
        const rolePermissionTable = config.get('rolePermission.role_permission_table', 'role_has_permissions');

        const roleTable = config.get('rolePermission.role_table', 'roles');
        const userRoleTable = config.get('rolePermission.user_role_table', 'link_accounts_roles');

        let permissionPlaceHolder = '(';
        let placeholders = new Array(permissionNames.length).fill('?');
        permissionPlaceHolder += placeholders.join(',');
        permissionPlaceHolder += ')';

        let {
            rows: {
                0: { permissioncount },
            },
        } = await db.rawQuery(
            'SELECT count("p"."name") as permissionCount FROM ' +
                roleTable +
                ' r INNER JOIN ' +
                userRoleTable +
                ' ur ON ur.role_id=r.id AND "ur"."account_id"=? ' +
                ' INNER JOIN ' +
                rolePermissionTable +
                ' rp ON rp.role_id=r.id ' +
                ' INNER JOIN ' +
                permissionTable +
                ' p ON rp.permission_id=p.id AND "p"."name" in ' +
                permissionPlaceHolder +
                ' LIMIT 1',
            [user.id, ...permissionNames],
        );

        return permissioncount > 0;
    }
}

// export default User;
