import * as Constants from "../config/constants";
import { NotificationDao } from "../dao/notification.dao";
import { UserDao } from "../dao/user.dao";
import { LanguageService } from "../services/language.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";

export class NotificationService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private userDao: UserDao;
    private notificationDao: NotificationDao;
    private languageService: LanguageService;
    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.notificationDao = new NotificationDao({
            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.userDao = new UserDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config,
        });
    }

    // create a new notification template
    createNotification = async (data: NotificationCreateServiceInput): Promise<NotificationObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            let notificationObj: NotificationObject = {
                code: Common.slugify(data.code),
                status: data.status ?? Constants.NOTIFICATION.STATUS.ACTIVE,
                imageId: data.imageId,
            };
            let notificationContentObj: NotificationContentObject = { body: data.body, title: data.title };
            let messageReplacements = Common.fetchShortCodes(notificationContentObj.body as unknown as string);
            const replacementArray = [...new Set([...messageReplacements])];
            notificationObj.replacements = replacementArray.join(",");
            notificationContentObj.bodyText = (notificationContentObj.body
                ? await Common.convertHtmlToText(notificationContentObj.body.toString())
                : "") as unknown as Text;
            const doesExistInput: NotificationDoExistsByCodeDaoInput = { code: notificationObj.code };
            const exists = await this.notificationDao.doExistsByCode(doesExistInput, { transaction });
            if (!exists) {
                const createInput: NotificationCreateDaoInput = { notificationObj, notificationContentObj, languages };
                let notificationIdentifier: number = await this.notificationDao.create(createInput, { transaction });
                const setSortOrderInput: NotificationSetSortOrderDaoInput = { id: notificationIdentifier };
                await this.notificationDao.setSortOrder(setSortOrderInput, { transaction });
                const getByIdInput: NotificationGetByIdDaoInput = { id: notificationIdentifier };
                let notification: NotificationObjectInteface = await this.notificationDao.getNotificationById(getByIdInput, {
                    transaction,
                });
                await transaction.commit();
                return notification;
            }
            throw new AppError(400, "EMAIL_TEMPLATE_ALREADY_EXISTS", { name: "EMAIL_TEMPLATE_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 notification template
    updateNotification = async (data: NotificationUpdateServiceInput): Promise<NotificationObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            let notificationObj: NotificationObject = {
                code: data.code,
                status: data.status ?? Constants.NOTIFICATION.STATUS.ACTIVE,
                imageId: data.imageId,
            };
            let notificationContentObj: NotificationContentObject = { body: data.body, title: data.title };
            notificationContentObj.bodyText = (notificationContentObj.body
                ? await Common.convertHtmlToText(notificationContentObj.body.toString())
                : "") as unknown as Text;
            const doesExistInput: NotificationDoExistsByIdDaoInput = { id: data.id };
            const exists = await this.notificationDao.doExistsById(doesExistInput, { transaction });
            if (exists) {
                if (notificationObj.code) {
                    const codeInUseInput: NotificationDoExistsByCodeDaoInput = { code: notificationObj.code, excludeId: data.id };
                    let codeInUse = await this.notificationDao.doExistsByCode(codeInUseInput, { transaction });
                    if (codeInUse) {
                        throw new AppError(400, "EMAIL_TEMPLATE_ALREADY_EXISTS", { name: "EMAIL_TEMPLATE_ALREADY_EXISTS" });
                    }
                }
                const updateInput: NotificationUpdateDaoInput = {
                    id: data.id,
                    notificationObj,
                    notificationContentObj,
                    languages,
                };
                await this.notificationDao.update(updateInput, { transaction });
                const getByIdInput: NotificationGetByIdDaoInput = { id: data.id };
                let notification: NotificationObjectInteface = await this.notificationDao.getNotificationById(getByIdInput, {
                    transaction,
                });
                await transaction.commit();
                return notification;
            }
            throw new AppError(404, "EMAIL_TEMPLATE_TYPE_NOT_FOUND", { name: "EMAIL_TEMPLATE_TYPE_NOT_FOUND" });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // delete the existing notification template
    deleteNotification = async (data: NotificationDeleteServiceInput): Promise<NotificationObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const deleteInput: NotificationDeleteDaoInput = { id: data.id };
            await this.notificationDao.delete(deleteInput, { transaction });
            const getByIdInput: NotificationGetByIdDaoInput = { id: data.id, expanded: true, paranoid: false };
            let notification: NotificationObjectInteface = await this.notificationDao.getNotificationById(getByIdInput, {
                transaction,
            });
            await transaction.commit();
            return notification;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // get notification template by id
    getById = async (data: NotificationGetByIdServiceInput): Promise<NotificationObjectInteface> => {
        try {
            const getByIdInput: NotificationGetByIdDaoInput = { id: data.id, expanded: data.expanded ?? true };
            return await this.notificationDao.getNotificationById(getByIdInput);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // get notification template by code
    getByCode = async (data: NotificationGetByCodeServiceInput): Promise<NotificationObjectInteface> => {
        try {
            const getByCodeInput: NotificationGetByCodeDaoInput = { code: data.code, expanded: data.expanded ?? true };
            return await this.notificationDao.getNotificationByCode(getByCodeInput);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // list notification templates
    getNotifications = async (data: NotificationGetNotificationsServiceInput): Promise<NotificationPaginatedList> => {
        try {
            const { listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = data;
            const { page, perPage } = listRequest;
            const getListInput: NotificationGetListDaoInput = { listRequest, language };
            let notifications: NotificationPaginatedData = await this.notificationDao.getNotificationList(getListInput);
            let totalPages = Common.getTotalPages(notifications.count, perPage);
            return {
                data: notifications.rows,
                page: page,
                perPage: perPage,
                totalRecords: notifications.count,
                totalPages: totalPages,
            } as unknown as NotificationPaginatedList;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // list all notification templates
    getAllNotifications = async (
        data: NotificationGetAllNotificationsServiceInput
    ): Promise<NotificationObjectSummaryInteface[]> => {
        try {
            const getAllInput: NotificationGetAllDaoInput = {
                listRequest: data.listRequest,
                language: data.language ?? process.env.DEFAULT_LANGUAGE_CODE!,
            };
            let notifications: NotificationObjectSummaryInteface[] = await this.notificationDao.getAllNotifications(getAllInput);
            return notifications;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // list notification template revisions
    getNotificationsRevisions = async (
        data: NotificationGetNotificationsRevisionsServiceInput
    ): Promise<NotificationPaginatedList> => {
        try {
            const { id, listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = data;
            const { page, perPage } = listRequest;
            const getRevisionListInput: NotificationGetRevisionListDaoInput = { id, listRequest, language };
            let notifications: NotificationPaginatedData = await this.notificationDao.getNotificationRevisionList(getRevisionListInput);
            let totalPages = Common.getTotalPages(notifications.count, perPage);
            return {
                data: notifications.rows,
                page: page,
                perPage: perPage,
                totalRecords: notifications.count,
                totalPages: totalPages,
            } as unknown as NotificationPaginatedList;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // Restore revision
    restoreRevision = async (data: NotificationRestoreRevisionServiceInput): Promise<NotificationObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restoreInput: NotificationRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.notificationDao.restoreRevision(restoreInput, { transaction });
            const getByIdInput: NotificationGetByIdDaoInput = { id: restoredEntiryId };
            let notification = await this.notificationDao.getNotificationById(getByIdInput, { transaction });
            await transaction.commit();
            return notification;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // get notification template id from code
    getNotificationId = async (data: NotificationGetNotificationIdServiceInput): Promise<number> => {
        try {
            const getIdInput: NotificationGetIdFromCodeDaoInput = { code: data.code };
            return await this.notificationDao.getIdFromCode(getIdInput);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // send notification template
    sendNotification = async (data: NotificationSendServiceInput) => {
        try {
            const getByCodeInput: NotificationGetByCodeServiceInput = { code: data.code };
            let notification = await this.getByCode(getByCodeInput);
            let content = (notification.body as unknown as string) ?? "";
            let text = await Common.convertHtmlToText(content);
            await Common.applyReplacements(text, data.replacements ?? {});
            const existingDevice = await this.userDao.getUserDevice(this.userId, this.accountId);
            if (!existingDevice) {
                throw new AppError(404, "USER_DEVICE_NOT_FOUND", { id: "USER_DEVICE_NOT_FOUND" });
            }
            return;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // set sort order
    setSortOrder = async (data: NotificationSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setSortOrderInput: NotificationSetSortOrderDaoInput = {
                id: data.id,
                before: data.before,
                after: data.after,
            };
            let sortOrder: boolean = await this.notificationDao.setSortOrder(setSortOrderInput, { 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 notification Template status
    updateStatus = async (data: NotificationUpdateStatusServiceInput): Promise<NotificationObjectInteface> => {
        try {
            const getByIdInput: NotificationGetByIdDaoInput = { id: data.id };
            let notification = await this.notificationDao.getNotificationById(getByIdInput);
            if (notification) {
                const updateStatusInput: NotificationUpdateStatusDaoInput = { id: data.id, status: data.status };
                await this.notificationDao.updateStatus(updateStatusInput);
                let notificationObject = await this.notificationDao.getNotificationById(getByIdInput);
                return notificationObject;
            }
            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);
        }
    };
}
