import { CommentDao } from '../dao/comment.dao';
import { PostDao } from '../dao/post.dao';
import { AppError } from '../../utils/errors';
import { COMMENT, POST } from '../config/constants';
import Models from '../models';
import { Op } from 'sequelize';
import { NotificationLogService } from './notificationLog.service';

export class CommentService {
    private commentDao: CommentDao;
    private userId: number | null;
    private config: userConfig | null;
    private accountId: number | null;
    private language: string;
    private scope: string[] | null;
    private postDao: PostDao;

    constructor(options: { userId: number | null, accountId: number | null, language?: string, scope?: string[] | null, config?: userConfig | null }) {
        this.commentDao = new CommentDao();
        this.userId = options.userId;
        this.config = options.config || null;
        this.accountId = options.accountId ?? null;
        this.language = options.language || process.env.DEFAULT_LANGUAGE_CODE!;
        this.scope = options.scope ?? [];
        this.postDao = new PostDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }

    private isAdmin = (): boolean => {
        return (this.scope || []).includes('admin') || (this.scope || []).includes('managePost');
    }

    private assertPostPublished = async (postId: number): Promise<PostObjectInteface> => {
        const post = await this.postDao.getPostById({ id: postId, postType: '', expanded: false, paranoid: true, trackIncludes: false }, {});

        if (post.status !== POST.STATUS.PUBLISH) {
            throw new AppError(403, 'COMMENT_NOT_ALLOWED', { message: 'Comments are allowed only on published posts' });
        }

        return post;
    }

    private assertPostCommentable = async (postId: number): Promise<PostObjectInteface> => {
        const post = await this.assertPostPublished(postId);

        if (this.isAdmin()) {
            return post;
        }

        const isPremium = this.config?.isPremium || false;
        const requiresSub = !!post.isExclusive;

        if (requiresSub && !isPremium) {
            throw new AppError(403, 'SUBSCRIPTION_REQUIRED', { message: 'Subscription required to comment' });
        }

        return post;
    }

    private assertParentComment = async (postId: number, parentId: number): Promise<void> => {
        const parent = await this.commentDao.getById(parentId);
        if (!parent) throw new AppError(404, 'COMMENT_NOT_FOUND', {});
        if (parent.postId !== postId) throw new AppError(400, 'INVALID_PARENT_COMMENT', {});
    }

    createComment = async (request: CommentCreateRequestObject): Promise<CommentObjectInterface> => {
        if (!this.userId) {
            throw new AppError(401, 'LOGIN_REQUIRED', { message: 'Guests cannot comment' });
        }

        const postObject = await this.assertPostCommentable(request.postId);
        if (request.parentId) {
            await this.assertParentComment(request.postId, request.parentId);
        }

        const commentObj: CommentInterface = {
            userId: this.userId,
            postId: request.postId,
            parentId: request.parentId || null,
            content: request.content,
            status: COMMENT.STATUS.APPROVED
        };

        const comment = await this.commentDao.create(commentObj);
        // Increment author's postCommentCount
        await Models.UserProfile.increment('postCommentCount', { where: { userId: this.userId } });

        // Notify admins, superadmins, and post author (only if comment is not by an admin)
        if (!(this.scope || []).includes('admin') && !(this.scope || []).includes('superadmin')) {
            try {
                if (postObject) {
                    const adminRoles = await Models.Role.findAll({
                        where: { code: { [Op.in]: ['admin', 'superadmin'] } },
                        attributes: ['id']
                    });
                    const adminRoleIds = adminRoles.map((r: any) => r.id);

                    const adminUsers = await Models.UserRole.findAll({
                        where: { roleId: { [Op.in]: adminRoleIds } },
                        attributes: ['userId']
                    });
                    const targetUserIds = adminUsers.map((ur: any) => ur.userId);

                    if (postObject.author?.id) {
                        targetUserIds.push(postObject.author.id);
                    }

                    const uniqueUserIds = ([...new Set(targetUserIds)] as number[]).filter(id => id !== this.userId);

                    const promises = uniqueUserIds.map((targetUserId: number) =>
                        NotificationLogService.emitByCode({
                            userId: targetUserId,
                            notificationCode: 'admin_comment_on_post',
                            fromUserId: this.userId!,
                            replacements: {
                                type: 'post',
                                postId: String(request.postId),
                                title: postObject.title,
                                data: {
                                    postId: String(request.postId),
                                    commentId: String(comment.id),
                                    title: postObject.title,
                                    code: postObject.code,
                                    content: comment.content
                                }
                            }
                        })
                    );
                    await Promise.allSettled(promises);
                }
            } catch (err) {
                console.error("Failed to notify new comment", err);
            }
        }

        return await this.getById(comment.id!) as CommentObjectInterface;
    }

    getById = async (id: number): Promise<CommentObjectInterface> => {
        const comment = await this.commentDao.getById(id);
        if (!comment) throw new AppError(404, 'COMMENT_NOT_FOUND', {});
        await this.assertPostPublished(comment.postId);
        return comment;
    }

    getList = async (listRequest: CommentListRequestObject): Promise<CommentPaginatedList> => {
        if (!listRequest.status) {
            listRequest.status = COMMENT.STATUS.APPROVED;
        }

        await this.assertPostPublished(listRequest.postId);

        const paginatedData = await this.commentDao.getList(listRequest);
        return {
            page: listRequest.page,
            perPage: listRequest.perPage,
            totalRecords: paginatedData.count,
            totalPages: Math.ceil(paginatedData.count / listRequest.perPage),
            data: paginatedData.rows
        };
    }

    updateStatus = async (id: number, status: string): Promise<CommentObjectInterface> => {
        if (!this.userId) throw new AppError(401, 'LOGIN_REQUIRED', { message: 'Login required' });
        const comment = await this.getById(id);
        if (!this.isAdmin() && comment.userId !== this.userId) {
            throw new AppError(403, 'NOT_AUTHORIZED', {});
        }
        await this.commentDao.update(id, { status });
        return await this.getById(id);
    }

    updateContent = async (id: number, payload: CommentUpdateRequestObject): Promise<CommentObjectInterface> => {
        if (!this.userId) throw new AppError(401, 'LOGIN_REQUIRED', { message: 'Login required' });
        const comment = await this.getById(id);
        if (!this.isAdmin() && comment.userId !== this.userId) {
            throw new AppError(403, 'NOT_AUTHORIZED', {});
        }

        await this.assertPostPublished(comment.postId);

        if (payload.postId !== undefined && payload.postId !== null && payload.postId !== comment.postId) {
            throw new AppError(400, 'INVALID_POST', {});
        }

        await this.commentDao.update(id, { content: payload.content });
        return await this.getById(id);
    }

    deleteComment = async (id: number): Promise<void> => {
        const comment = await this.getById(id);
        if (comment.userId !== this.userId && !this.isAdmin()) {
            throw new AppError(403, 'NOT_AUTHORIZED', {});
        }
        await this.commentDao.delete(id);
        // Decrement author's postCommentCount (never below 0)
        if (comment.userId) {
            await Models.UserProfile.decrement('postCommentCount', {
                where: { userId: comment.userId, postCommentCount: { [Op.gt]: 0 } }
            });
        }
    }
}
