import Models from '../models';
import { AppError } from '../../utils/errors';
import { WhereOptions, Includeable, literal } from 'sequelize';

export class CommentDao {
    private buildInclude = (replyLimit: number | null, includeReplies: boolean): Includeable[] => {
        const buildAuthorInclude = (): Includeable => ({
            model: Models.User,
            as: 'author',
            attributes: [
                'id',
                'onlineStatus',
                [literal('COALESCE(`author->userProfile`.`name`, `author`.`username`)'), 'name'] as any,
                [
                    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}', '/attachment/', a.unique_name)
                        )
                        FROM user_profile as up
                        JOIN attachments as a ON up.profile_image_id = a.id
                        WHERE up.user_id = author.id
                        LIMIT 1
                    )`),
                    'profileImage'
                ] as any,
                [
                    literal(`(
                        SELECT cc.name
                        FROM user_profile as up
                        JOIN category_content as cc ON up.rank_id = cc.category_id
                        WHERE up.user_id = author.id
                        LIMIT 1
                    )`),
                    'rank'
                ] as any
            ],
            include: [
                {
                    model: Models.UserProfile,
                    as: 'userProfile',
                    attributes: []
                }
            ]
        } as unknown as Includeable);

        const includes: Includeable[] = [buildAuthorInclude()];

        if (includeReplies) {
            includes.push({
                model: Models.Comment,
                as: 'replies',
                separate: true,
                ...(replyLimit ? { limit: replyLimit } : {}),
                order: [['createdAt', 'ASC']],
                include: [buildAuthorInclude()]
            });
        }

        return includes;
    }

    private mapCommentAuthors = (comments: any[]): void => {
        for (const comment of comments) {
            if (!comment.author && (comment.commentAuthorName || comment.commentAuthorEmail)) {
                comment.author = {
                    id: 0,
                    onlineStatus: 0,
                    name: comment.commentAuthorName || 'Guest',
                    profileImage: null
                };
            }
            if (comment.replies && comment.replies.length > 0) {
                this.mapCommentAuthors(comment.replies);
            }
        }
    }

    create = async (commentObj: CommentInterface, daoOptions: any = {}): Promise<CommentInterface> => {
        try {
            const comment = await Models.Comment.create(commentObj, daoOptions);
            return comment;
        } catch (err) {
            throw new AppError(500, 'ERROR_WHILE_CREATING_COMMENT', err);
        }
    }

    update = async (id: number, commentObj: Partial<CommentInterface>, daoOptions: any = {}): Promise<[number, CommentInterface[]]> => {
        try {
            const result = await Models.Comment.update(commentObj, {
                where: { id },
                ...daoOptions
            });
            return result;
        } catch (err) {
            throw new AppError(500, 'ERROR_WHILE_UPDATING_COMMENT', err);
        }
    }

    delete = async (id: number, daoOptions: any = {}): Promise<number> => {
        try {
            const result = await Models.Comment.destroy({
                where: { id },
                ...daoOptions
            });
            return result;
        } catch (err) {
            throw new AppError(500, 'ERROR_WHILE_DELETING_COMMENT', err);
        }
    }

    getById = async (id: number, daoOptions: any = {}): Promise<CommentObjectInterface | null> => {
        try {
            const comment = await Models.Comment.findByPk(id, {
                attributes: {
                    include: [
                        [literal(`(SELECT COUNT(*) FROM comments AS reply WHERE reply.parent_id = Comment.id)`), 'repliesCount']
                    ]
                },
                include: this.buildInclude(null, true),
                ...daoOptions
            });
            if (comment) {
                const parsed = JSON.parse(JSON.stringify(comment));
                this.mapCommentAuthors([parsed]);
                
                const repliesCount = parsed.repliesCount || (parsed.replies ? parsed.replies.length : 0);
                parsed.replies = {
                    page: 1,
                    perPage: parsed.replies ? parsed.replies.length : null,
                    totalRecords: repliesCount,
                    totalPages: 1,
                    data: parsed.replies || []
                };
                delete parsed.repliesCount;

                return parsed as any;
            }
            return null;
        } catch (err) {
            throw new AppError(500, 'ERROR_WHILE_FETCHING_COMMENT', err);
        }
    }

    getList = async (listRequest: CommentListRequestObject, daoOptions: any = {}): Promise<CommentPaginatedData> => {
        try {
            const { page, perPage, sortBy, sortDirection, postId, parentId, status, userId } = listRequest;
            const where: WhereOptions = { postId };

            if (parentId !== undefined) {
                where.parentId = parentId;
            } else {
                where.parentId = null; // Default to root comments
            }

            if (status) {
                where.status = status;
            }
            if (userId) {
                where.userId = userId;
            }

            const offset = (page - 1) * perPage;
            const limit = perPage;
            const includeReplies = parentId === undefined || parentId === null;
            const include = this.buildInclude(includeReplies ? perPage : null, includeReplies);

            const { count, rows } = await Models.Comment.findAndCountAll({
                where,
                offset,
                limit,
                order: [[sortBy || 'createdAt', sortDirection || 'DESC']],
                attributes: {
                    include: [
                        [literal(`(SELECT COUNT(*) FROM comments AS reply WHERE reply.parent_id = Comment.id)`), 'repliesCount']
                    ]
                },
                include,
                distinct: true,
                ...daoOptions
            });

            const parsedRows = JSON.parse(JSON.stringify(rows));
            this.mapCommentAuthors(parsedRows);

            if (includeReplies) {
                for (const comment of parsedRows) {
                    const repliesCount = comment.repliesCount || (comment.replies ? comment.replies.length : 0);
                    comment.replies = {
                        page: 1,
                        perPage: perPage,
                        totalRecords: repliesCount,
                        totalPages: Math.ceil(repliesCount / (perPage || 1)),
                        data: comment.replies || []
                    };
                    delete comment.repliesCount;
                }
            }

            return { count, rows: parsedRows as any };
        } catch (err) {
            throw new AppError(500, 'ERROR_WHILE_FETCHING_COMMENT_LIST', err);
        }
    }

    getByWpCommentId = async (wpCommentId: number, daoOptions: any = {}): Promise<CommentInterface | null> => {
        try {
            const comment = await Models.Comment.findOne({
                where: { wpCommentId },
                ...daoOptions
            });
            return comment ? comment.get({ clone: true }) as any : null;
        } catch (err) {
            throw new AppError(500, 'ERROR_WHILE_FETCHING_COMMENT_BY_WP_ID', err);
        }
    }
}
