import { RoleDao } from "../dao/role.dao";
import { LanguageService } from "../services/language.service";
import { PermissionService } from "../services/permission.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { ROLE } from "../config/constants";

export class RoleService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private roleDao: RoleDao;
    private languageService: LanguageService;
    private permissionService: PermissionService;
    constructor(
        private options: {
            language: string;
            scope: string[] | null;
            accountId: number | null;
            userId: number | null;
            config: userConfig | null;
        } = {
                language: process.env.DEFAULT_LANGUAGE_CODE!,
                scope: null,
                config: null,
                userId: null,
                accountId: null,
            }
    ) {
        this.language = options.language;
        this.scope = options.scope ?? [];
        this.config = options.config ?? null;
        this.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.roleDao = new RoleDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.permissionService = new PermissionService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.languageService = new LanguageService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }

    // create a new content type
    createRole = async (data: RoleCreateServiceInput): Promise<RoleObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { name, description, permissionIds, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            let roleObj: RoleObject = { code: Common.slugify(name), status: status ?? ROLE.STATUS.ACTIVE };
            let roleContentObj: RoleContentObject = { name, description };
            if (['admin', 'superadmin', 'user', 'instructor'].includes(roleObj.code!)) {
                throw new AppError(400, 'RESERVED_SYSTEM_ROLE', { name: 'RESERVED_SYSTEM_ROLE' });
            }
            roleContentObj.descriptionText = (roleContentObj.description ? await Common.convertHtmlToText(roleContentObj.description.toString()) : null) as unknown as Text;
            const doesRoleExistInput: RoleDoExistsByCodeDaoInput = { code: roleObj.code! };
            const exists = await this.roleDao.doExistsByCode(doesRoleExistInput, { transaction });
            if (!exists) {
                const verifyPermissionsInput: PermissionVerifyPermissionServiceInput = { ids: permissionIds };
                const verifyPermissions = await this.permissionService.verifyPermission(verifyPermissionsInput);
                if (permissionIds && verifyPermissions.length != permissionIds.length) {
                    throw new AppError(400, 'INVALID_DUPLICATE_PERMISSIONS_FOUND', { permissionIds: 'INVALID_DUPLICATE_PERMISSIONS_FOUND' });
                }
                const createRoleInput: RoleCreateDaoInput = { roleObj, roleContentObj, permissionIds, languages };
                let roleIdentifier: number = await this.roleDao.create(createRoleInput, { transaction });
                const setRoleSortOrderInput: RoleSetSortOrderDaoInput = { id: roleIdentifier };
                await this.roleDao.setSortOrder(setRoleSortOrderInput, { transaction });
                const getRoleByIdInput: RoleGetByIdDaoInput = { id: roleIdentifier };
                let role = await this.roleDao.getRoleById(getRoleByIdInput, { transaction });
                await transaction.commit();
                return role;
            }
            throw new AppError(400, 'ROLE_ALREADY_EXISTS', { name: 'ROLE_ALREADY_EXISTS' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing content type
    updateRole = async (data: RoleUpdateServiceInput): Promise<RoleObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, name, description, permissionIds, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            let roleObj: RoleObject = { code: '-', status: status ?? ROLE.STATUS.ACTIVE };
            let roleContentObj: RoleContentObject = { name, description };
            roleObj.code = (languages.requested.code == process.env.DEFAULT_LANGUAGE_CODE ? Common.slugify(roleContentObj.name) : undefined);
            if (roleObj.code) {
                if (['admin', 'superadmin', 'user'].includes(roleObj.code)) {
                    throw new AppError(400, 'RESERVED_SYSTEM_ROLE', { name: 'RESERVED_SYSTEM_ROLE' });
                }
            }
            roleContentObj.descriptionText = (roleContentObj.description ? await Common.convertHtmlToText(roleContentObj.description.toString()) : null) as unknown as Text;
            const doesRoleExistInput: RoleDoExistsByIdDaoInput = { id };
            const exists = await this.roleDao.doExistsById(doesRoleExistInput, { transaction });
            if (exists) {
                const verifyPermissionsInput: PermissionVerifyPermissionServiceInput = { ids: permissionIds };
                const verifyPermissions = await this.permissionService.verifyPermission(verifyPermissionsInput);
                if (permissionIds && verifyPermissions.length != permissionIds.length) {
                    throw new AppError(400, 'INVALID_DUPLICATE_PERMISSIONS_FOUND', { permissionIds: 'INVALID_DUPLICATE_PERMISSIONS_FOUND' });
                }
                if (roleObj.code) {
                    const codeInUseInput: RoleDoExistsByCodeDaoInput = { code: roleObj.code, excludeId: id };
                    let codeInUse = await this.roleDao.doExistsByCode(codeInUseInput, { transaction });
                    if (codeInUse) {
                        throw new AppError(400, 'ROLE_ALREADY_EXISTS', { name: 'ROLE_ALREADY_EXISTS' });
                    }
                }
                const updateRoleInput: RoleUpdateDaoInput = { id, roleObj, roleContentObj, permissionIds, languages };
                await this.roleDao.update(updateRoleInput, { transaction });
                const getRoleByIdInput: RoleGetByIdDaoInput = { id };
                let role = await this.roleDao.getRoleById(getRoleByIdInput, { transaction });
                await transaction.commit();
                return role;
            }
            throw new AppError(404, 'ROLE_NOT_FOUND', { name: 'ROLE_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing content type
    deleteRole = async (data: RoleDeleteServiceInput): Promise<RoleObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const doesRoleExistInput: RoleDoExistsByIdDaoInput = { id };
            const exists = await this.roleDao.doExistsById(doesRoleExistInput, { transaction });
            if (exists) {
                const deleteRoleInput: RoleDeleteDaoInput = { id };
                await this.roleDao.delete(deleteRoleInput, { transaction });
                const getRoleByIdInput: RoleGetByIdDaoInput = { id, expanded: false, paranoid: false };
                let role = await this.roleDao.getRoleById(getRoleByIdInput, { transaction });
                await transaction.commit();
                return role;
            }
            throw new AppError(404, 'ROLE_NOT_FOUND', { name: 'ROLE_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get role Type by id
    getById = async (data: RoleGetByIdServiceInput): Promise<RoleObjectInteface> => {
        try {
            const getRoleByIdInput: RoleGetByIdDaoInput = {
                id: data.id,
                expanded: data.expanded ?? true,
                paranoid: data.paranoid ?? true
            };
            let role = await this.roleDao.getRoleById(getRoleByIdInput);
            if (role) {
                return role;
            }
            throw new AppError(404, 'ROLE_NOT_FOUND', { name: 'ROLE_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get role Type by code
    getByCode = async (data: RoleGetByCodeServiceInput): Promise<RoleObjectInteface> => {
        try {
            const getRoleByCodeInput: RoleGetByCodeDaoInput = {
                code: data.code,
                expanded: data.expanded ?? true,
                paranoid: data.paranoid ?? true
            };
            let role = await this.roleDao.getRoleByCode(getRoleByCodeInput);
            if (role) {
                return role;
            }
            throw new AppError(404, 'ROLE_NOT_FOUND', { name: 'ROLE_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list role types
    listRoles = async (data: RoleListRolesServiceInput) => {
        try {
            const { listRequest } = data;
            const { page, perPage } = listRequest;
            const getRoleListInput: RoleGetRoleListDaoInput = { listRequest };
            let roles = await this.roleDao.getRoleList(getRoleListInput);
            if (roles) {
                let totalPages = Common.getTotalPages(roles.count, perPage);
                return {
                    data: roles.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: roles.count,
                    totalPages: totalPages
                };
            }
            throw new AppError(400, 'ROLE_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list all role types
    listAllRoles = async (data: RoleListAllRolesServiceInput) => {
        try {
            const getAllRolesInput: RoleGetAllRolesDaoInput = { listRequest: data.listRequest };
            let roles = await this.roleDao.getAllRoles(getAllRolesInput);
            if (roles) {
                return roles;
            }
            throw new AppError(400, 'ROLE_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list role types revisions
    listRoleRevisions = async (data: RoleListRoleRevisionsServiceInput) => {
        try {
            const { id, listRequest } = data;
            const { page, perPage } = listRequest;
            const getRoleRevisionListInput: RoleGetRoleRevisionListDaoInput = { id, listRequest };
            let roles = await this.roleDao.getRoleRevisionList(getRoleRevisionListInput);
            if (roles) {
                let totalPages = Common.getTotalPages(roles.count, perPage);
                return {
                    data: roles.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: roles.count,
                    totalPages: totalPages
                };
            }
            throw new AppError(400, 'ROLE_REVISION_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Restore revision
    restoreRevision = async (data: RoleRestoreRevisionServiceInput) => {
        const transaction = await sequelize.transaction();
        try {
            const restoreRoleRevisionInput: RoleRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.roleDao.restoreRevision(restoreRoleRevisionInput, { transaction });
            if (restoredEntiryId) {
                const getRoleByIdInput: RoleGetByIdDaoInput = { id: restoredEntiryId };
                let role = await this.roleDao.getRoleById(getRoleByIdInput, { transaction });
                await transaction.commit();
                return role;
            }
            throw new AppError(400, 'ROLE_RESTORE_REVISION_ERROR_OUT', {});
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get default roles
    getDafaultRoles = async (): Promise<number[]> => {
        try {
            let defaultRoles = await this.roleDao.getDafaultRoles();
            return defaultRoles;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // set sort order
    setSortOrder = async (data: RoleSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setRoleSortOrderInput: RoleSetSortOrderDaoInput = { id: data.id, before: data.before, after: data.after };
            let sortOrder: boolean = await this.roleDao.setSortOrder(setRoleSortOrderInput, { transaction });
            await transaction.commit();
            return sortOrder;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update quote status
    updateStatus = async (data: RoleUpdateStatusServiceInput): Promise<RoleObjectInteface> => {
        try {
            const getRoleByIdInput: RoleGetByIdDaoInput = { id: data.id };
            let role = await this.roleDao.getRoleById(getRoleByIdInput);
            if (role) {
                const updateRoleStatusInput: RoleUpdateStatusDaoInput = { id: data.id, status: data.status };
                await this.roleDao.updateStatus(updateRoleStatusInput);
                const getRoleByIdServiceInput: RoleGetByIdServiceInput = { id: data.id, expanded: true };
                let roleObject = await this.getById(getRoleByIdServiceInput);
                return roleObject;
            } else {
                throw new AppError(404, 'QUOTE_NOT_FOUND', { id: 'QUOTE_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get staff roles
    staffRoles = async () => {
        try {
            let staffRoles = await this.roleDao.getStaffRoles();
            return staffRoles;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get user roles
    userRoles = async () => {
        try {
            let staffRoles = await this.roleDao.getUserRoles();
            return staffRoles;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
