import { fn, literal, Op } from "sequelize";
import Models from "../models";
import { AppError } from "../../utils/errors";

export class NotificationLogDao {
    private language: string;

    constructor(
        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;
    }

    create = async (data: NotificationLogCreateDaoInput, options: DaoOptions = {}): Promise<NotificationLogObjectInterface> => {
        try {
            const { userId, notificationId, replacements } = data;
            const log = await Models.NotificationLog.create(
                { userId, notificationId, fromUserId: data.fromUserId ?? null, replacements, isViewed: false, isRead: false },
                { transaction: options.transaction }
            );
            return await this.getById({ id: log.id }, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getById = async (data: NotificationLogGetByIdDaoInput, options: DaoOptions = {}): Promise<NotificationLogObjectInterface> => {
        try {
            const { id } = data;
            const log = await Models.NotificationLog.findOne({
                where: { id },
                include: this.includeAssociations(),
                transaction: options.transaction
            });
            if (!log) {
                throw new AppError(404, 'NOTIFICATION_LOG_NOT_FOUND', { id: 'NOTIFICATION_LOG_NOT_FOUND' });
            }
            return JSON.parse(JSON.stringify(log)) as NotificationLogObjectInterface;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getList = async (data: NotificationLogGetListDaoInput, options: DaoOptions = {}): Promise<NotificationLogPaginatedData> => {
        try {
            const { userId, listRequest } = data;
            const { page, perPage, isViewed, isRead } = listRequest;
            const offset = (page - 1) * perPage;
            const where: any = {
                userId,
                [Op.or]: [
                    { fromUserId: { [Op.ne]: userId } },
                    { fromUserId: null }
                ]
            };
            if (isViewed !== null && isViewed !== undefined) where.isViewed = isViewed;
            if (isRead !== null && isRead !== undefined) where.isRead = isRead;

            const logs = await Models.NotificationLog.findAndCountAll({
                where,
                include: this.includeAssociations(),
                offset,
                limit: perPage,
                order: [['id', 'DESC']],
                subQuery: false,
                transaction: options.transaction
            });
            return JSON.parse(JSON.stringify(logs)) as NotificationLogPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getUnreadCount = async (userId: number, options: DaoOptions = {}): Promise<number> => {
        try {
            return await Models.NotificationLog.count({
                where: {
                    userId,
                    isRead: false,
                    [Op.or]: [
                        { fromUserId: { [Op.ne]: userId } },
                        { fromUserId: null }
                    ]
                },
                transaction: options.transaction
            });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    markViewed = async (data: NotificationLogMarkViewedDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.NotificationLog.update(
                { isViewed: true },
                { where: { id: data.id, userId: data.userId }, transaction: options.transaction }
            );
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    markRead = async (data: NotificationLogMarkReadDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.NotificationLog.update(
                { isRead: true },
                { where: { id: data.id, userId: data.userId }, transaction: options.transaction }
            );
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    private includeAssociations = () => {
        const language = this.language;
        return [
            {
                model: Models.User,
                as: 'fromUser',
                attributes: [
                    'id',
                    [literal('`fromUser->userProfile`.`name`'), 'name'],
                    [literal(`(
                        SELECT JSON_OBJECT(
                            'id', a.id,
                            'fileName', a.file_name,
                            'uniqueName', a.unique_name,
                            'filePath', CONCAT('${process.env.PROTOCOL}://', '${process.env.API_HOST}', '/attachment/', a.unique_name),
                            'cdnUrl', CONCAT('${process.env.CDN_PATH}', a.file_path)
                        )
                        FROM user_profile AS up
                        JOIN attachments AS a ON up.profile_image_id = a.id
                        WHERE up.user_id = \`fromUser\`.\`id\`
                        LIMIT 1
                    )`), 'profileImage'],
                ],
                required: false,
                include: [
                    { attributes: [], model: Models.UserProfile, as: 'userProfile', required: false }
                ]
            },
            {
                model: Models.Notification,
                as: 'notification',
                attributes: [
                    'id', 'code', 'replacements',
                    [literal('(case when `notification->content`.`title` is not null then `notification->content`.`title` else `notification->defaultContent`.`title` END)'), 'title'],
                    [literal('(case when `notification->content`.`body` is not null then `notification->content`.`body` else `notification->defaultContent`.`body` END)'), 'body'],
                    [literal('(case when `notification->content`.`body_text` is not null then `notification->content`.`body_text` else `notification->defaultContent`.`body_text` END)'), 'bodyText'],
                ],
                include: [
                    {
                        attributes: [],
                        model: Models.NotificationContent,
                        as: 'content',
                        required: false,
                        include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: language }, required: false }]
                    },
                    {
                        attributes: [],
                        model: Models.NotificationContent,
                        as: 'defaultContent',
                        required: false,
                        include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE }, required: false }]
                    },
                    {
                        attributes: [
                            'id', 'fileName', 'uniqueName',
                            [fn('CONCAT', process.env.PROTOCOL, '://', process.env.API_HOST, '/attachment/', literal('`notification->notificationImage`.`unique_name`')), 'filePath'],
                            [fn('CONCAT', process.env.CDN_PATH, literal('`notification->notificationImage`.`file_path`')), 'cdnUrl'],
                        ],
                        model: Models.Attachment,
                        as: 'notificationImage',
                        required: false,
                    }
                ]
            }
        ];
    }
}
