import { PermissionDao } from "../dao/permission.dao";
import { LanguageService } from "../services/language.service";
import { CategoryService } from "../services/category.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { PERMISSION } from "../config/constants";

export class PermissionService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private permissionDao: PermissionDao;
    private languageService: LanguageService;
    private categoryService: CategoryService;
    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.permissionDao = new PermissionDao({
            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
        });
        this.categoryService = new CategoryService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });

    }

    // revision settings
    public isRevisionEnabled: boolean = (process.env.REVISION_ENABLED !== 'true' ? true : false) || false;

    // create a new content type
    createPermission = async (data: PermissionCreateServiceInput): Promise<PermissionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { name, description, categoryCode, status, code } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            const getCategoryIdInput: PermissionGetCategoryIdServiceInput = { categoryCode: categoryCode as string };
            let categoryId = categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            let permissionObj: PermissionObject = { categoryId: categoryId, status: status ?? PERMISSION.STATUS.ACTIVE, code: code };
            let permissionContentObj: PermissionContentObject = { name: name, description: description };
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: permissionObj.categoryId as number, categoryTypeCode: 'permission' };
            let categoryVerification = permissionObj.categoryId ? await this.categoryService.verifyCategoryId(verifyCategoryIdInput) : true;
            if (!categoryVerification) {
                throw new AppError(400, 'INVALID_PERMISSION_CATEGORY', { categoryId: 'INVALID_PERMISSION_CATEGORY' });
            }
            permissionContentObj.descriptionText = (permissionContentObj.description ? await Common.convertHtmlToText(permissionContentObj.description.toString()) : null) as unknown as Text;
            const doesPermissionExistInput: PermissionDoExistsByCodeDaoInput = { code: permissionObj.code };
            const exists = await this.permissionDao.doExistsByCode(doesPermissionExistInput, { transaction });
            if (!exists) {
                const createPermissionInput: PermissionCreateDaoInput = { permissionObj, permissionContentObj, languages };
                let permissionIdentifier: number = await this.permissionDao.create(createPermissionInput, { transaction });
                const setPermissionSortOrderInput: PermissionSetSortOrderDaoInput = { id: permissionIdentifier };
                await this.permissionDao.setSortOrder(setPermissionSortOrderInput, { transaction });
                const getPermissionByIdInput: PermissionGetByIdDaoInput = { id: permissionIdentifier };
                let permission: PermissionObjectInteface = await this.permissionDao.getPermissionById(getPermissionByIdInput, { transaction });
                await transaction.commit();
                return permission;
            }
            throw new AppError(400, 'PERMISSION_ALREADY_EXISTS', { name: 'PERMISSION_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
    updatePermission = async (data: PermissionUpdateServiceInput): Promise<PermissionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, name, description, categoryCode, status, code } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            const getCategoryIdInput: PermissionGetCategoryIdServiceInput = { categoryCode: categoryCode as string };
            let categoryId = categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            let permissionObj: PermissionObject = { categoryId, status: status ?? PERMISSION.STATUS.ACTIVE, code };
            let permissionContentObj: PermissionContentObject = { name, description };
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: permissionObj.categoryId as number, categoryTypeCode: 'permission' };
            let categoryVerification = permissionObj.categoryId ? await this.categoryService.verifyCategoryId(verifyCategoryIdInput) : true;
            if (!categoryVerification) {
                throw new AppError(400, 'INVALID_PERMISSION_CATEGORY', { categoryId: 'INVALID_PERMISSION_CATEGORY' });
            }
            let permissionCode = (languages.requested.code == process.env.DEFAULT_LANGUAGE_CODE ? permissionObj.code : null);
            if (permissionCode) {
                const codeInUseInput: PermissionDoExistsByCodeDaoInput = { code: permissionCode, excludeId: id };
                let codeInUse = await this.permissionDao.doExistsByCode(codeInUseInput, { transaction });
                if (codeInUse) {
                    throw new AppError(400, 'PERMISSION_ALREADY_EXISTS', { name: 'PERMISSION_ALREADY_EXISTS' });
                }
            }
            permissionContentObj.descriptionText = (permissionContentObj.description ? await Common.convertHtmlToText(permissionContentObj.description.toString()) : null) as unknown as Text;
            const doesPermissionExistInput: PermissionDoExistsByIdDaoInput = { id };
            const exists = await this.permissionDao.doExistsById(doesPermissionExistInput, { transaction });
            if (exists) {
                const updatePermissionInput: PermissionUpdateDaoInput = { id, permissionObj, permissionContentObj, languages };
                await this.permissionDao.update(updatePermissionInput, { transaction });
                const getPermissionByIdInput: PermissionGetByIdDaoInput = { id };
                let permission = await this.permissionDao.getPermissionById(getPermissionByIdInput, { transaction });
                await transaction.commit();
                return permission;
            }
            throw new AppError(404, 'PERMISSION_NOT_FOUND', { name: 'PERMISSION_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
    deletePermission = async (data: PermissionDeleteServiceInput): Promise<PermissionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const doesPermissionExistInput: PermissionDoExistsByIdDaoInput = { id };
            const exists = await this.permissionDao.doExistsById(doesPermissionExistInput, { transaction });
            if (exists) {
                const deletePermissionInput: PermissionDeleteDaoInput = { id };
                await this.permissionDao.delete(deletePermissionInput, { transaction });
                const getPermissionByIdInput: PermissionGetByIdDaoInput = { id, paranoid: false };
                let permission = await this.permissionDao.getPermissionById(getPermissionByIdInput, { transaction });
                await transaction.commit();
                return permission;
            }
            throw new AppError(404, 'PERMISSION_NOT_FOUND', { name: 'PERMISSION_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get category Type by id
    getById = async (data: PermissionGetByIdServiceInput): Promise<PermissionObjectInteface> => {
        try {
            const getPermissionByIdInput: PermissionGetByIdDaoInput = { id: data.id, paranoid: data.paranoid ?? true };
            let permission = await this.permissionDao.getPermissionById(getPermissionByIdInput);
            if (permission) {
                return permission;
            }
            throw new AppError(404, 'PERMISSION_NOT_FOUND', { name: 'PERMISSION_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get category Type by code
    getByCode = async (data: PermissionGetByCodeServiceInput): Promise<PermissionObjectInteface> => {
        try {
            const getPermissionByCodeInput: PermissionGetByCodeDaoInput = { code: data.code, paranoid: data.paranoid ?? true };
            let permission = await this.permissionDao.getPermissionByCode(getPermissionByCodeInput);
            if (permission) {
                return permission;
            }
            throw new AppError(404, 'PERMISSION_NOT_FOUND', { name: 'PERMISSION_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list category types
    getPermissions = async (data: PermissionGetPermissionsServiceInput): Promise<PermissionPaginatedList> => {
        try {
            const { listRequest } = data;
            const { page, perPage } = listRequest;
            const getCategoryIdInput: PermissionGetCategoryIdServiceInput = { categoryCode: listRequest.categoryCode as string };
            let categoryId = listRequest.categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            const getPermissionListInput: PermissionGetPermissionListDaoInput = { categoryId, listRequest };
            let permissions: PermissionPaginatedData = await this.permissionDao.getPermissionList(getPermissionListInput);
            if (permissions) {
                let totalPages = Common.getTotalPages(permissions.count, perPage);
                return {
                    data: permissions.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: permissions.count,
                    totalPages: totalPages
                } as unknown as PermissionPaginatedList;
            }
            throw new AppError(400, 'PERMISSION_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list all category types
    getAllPermissions = async (data: PermissionGetAllPermissionsServiceInput): Promise<PermissionObjectSummaryInteface[]> => {
        try {
            const { listRequest } = data;
            const getCategoryIdInput: PermissionGetCategoryIdServiceInput = { categoryCode: listRequest.categoryCode as string };
            let categoryId = listRequest.categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            const getAllPermissionsInput: PermissionGetAllPermissionsDaoInput = { categoryId, listRequest };
            let permissions = await this.permissionDao.getAllPermissions(getAllPermissionsInput);
            if (permissions) {
                return permissions;
            }
            throw new AppError(400, 'PERMISSION_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list category types revisions
    getPermissionsRevisions = async (data: PermissionGetPermissionsRevisionsServiceInput): Promise<PermissionPaginatedList> => {
        try {
            const { id, listRequest } = data;
            const { page, perPage } = listRequest;
            const getPermissionRevisionListInput: PermissionGetPermissionRevisionListDaoInput = { id, listRequest };
            let permissions: PermissionPaginatedData = await this.permissionDao.getPermissionRevisionList(getPermissionRevisionListInput);
            if (permissions) {
                let totalPages = Common.getTotalPages(permissions.count, perPage);
                return {
                    data: permissions.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: permissions.count,
                    totalPages: totalPages
                } as unknown as PermissionPaginatedList;
            }
            throw new AppError(400, 'PERMISSION_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: PermissionRestoreRevisionServiceInput): Promise<PermissionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restorePermissionRevisionInput: PermissionRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.permissionDao.restoreRevision(restorePermissionRevisionInput, { transaction });
            if (restoredEntiryId) {
                const getPermissionByIdInput: PermissionGetByIdDaoInput = { id: restoredEntiryId };
                let permission: PermissionObjectInteface = await this.permissionDao.getPermissionById(getPermissionByIdInput, { transaction });
                await transaction.commit();
                return permission;
            }
            throw new AppError(400, 'PERMISSION_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);
        }
    }

    // build permission tree for category
    private buildPermissionTree = async (categories: CategoryTree[]): Promise<PermissionTree[]> => {
        const tree: PermissionTree[] = [];
        for (const cat of categories) {
            const getCategoryByIdInput: CategoryGetByIdServiceInput = { id: cat.id, fullInfo: false };
            const category = await this.categoryService.getById(getCategoryByIdInput);
            let listRequest: PermissionListAllRequestObject = { searchText: null, sortBy: "sortOrder", sortDirection: "asc", status: PERMISSION.STATUS.ACTIVE, categoryCode: null };
            const getAllPermissionsInput: PermissionGetAllPermissionsServiceInput = { listRequest: { ...listRequest, categoryCode: cat.code } };
            const permissions = await this.getAllPermissions(getAllPermissionsInput);
            const childTree: PermissionTree[] = cat.childCategories?.length ? await this.buildPermissionTree(cat.childCategories) : [];
            tree.push({ category, permissions, childTree });
        }
        return tree;
    }

    // get permission tree
    permissionTree = async (): Promise<PermissionTree[]> => {
        try {
            const getCategoryTreeInput: CategoryGetCategoryTreeServiceInput = { categoryTypeCode: 'permission', code: null };
            let permissionCategories = await this.categoryService.getCategoryTree(getCategoryTreeInput);
            const permissionTree = await this.buildPermissionTree(permissionCategories);
            return permissionTree;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // set sort order
    setSortOrder = async (data: PermissionSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setPermissionSortOrderInput: PermissionSetSortOrderDaoInput = { id: data.id, before: data.before, after: data.after };
            await this.permissionDao.setSortOrder(setPermissionSortOrderInput, { transaction });
            await transaction.commit();
            return true;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update email Template status
    updateStatus = async (data: PermissionUpdateStatusServiceInput): Promise<PermissionObjectInteface> => {
        try {
            const getPermissionByIdInput: PermissionGetByIdDaoInput = { id: data.id };
            let permission = await this.permissionDao.getPermissionById(getPermissionByIdInput);
            if (permission) {
                const updatePermissionStatusInput: PermissionUpdateStatusDaoInput = { id: data.id, status: data.status };
                await this.permissionDao.updateStatus(updatePermissionStatusInput);
                const getPermissionByIdServiceInput: PermissionGetByIdServiceInput = { id: data.id };
                let permissionObject = await this.getById(getPermissionByIdServiceInput);
                return permissionObject;
            } else {
                throw new AppError(404, 'EMAIL_TEMPLATE_NOT_FOUND', { id: 'EMAIL_TEMPLATE_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    //get category Id
    getCategoryId = async (data: PermissionGetCategoryIdServiceInput): Promise<number | null> => {
        try {
            const getCategoryIdInput: CategoryGetCategoryIdServiceInput = { code: data.categoryCode };
            let categoryId = await this.categoryService.getCategoryId(getCategoryIdInput);
            return categoryId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // checkIf Permission exists
    verifyPermission = async (data: PermissionVerifyPermissionServiceInput): Promise<PermissionObjectInteface[]> => {
        try {
            const verifyPermissionsInput: PermissionVerifyPermissionsDaoInput = { ids: data.ids };
            let existence = await this.permissionDao.verifyPermissions(verifyPermissionsInput);
            return existence;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
