import { SeaQADao } from "../dao/seaQA.dao";
import { CategoryDao } from "../dao/category.dao";
import { sequelize } from "../models";
import Models from "../models";
import { Op } from "sequelize";
import { SeaQAQuestion, SeaQAQuestionAttributes, SeaQAQuestionCreationAttributes } from "../models/SeaQAQuestion";
import { SeaQAAnswer, SeaQAAnswerAttributes, SeaQAAnswerCreationAttributes } from "../models/SeaQAAnswer";
import { SeaQAComment, SeaQACommentAttributes, SeaQACommentCreationAttributes } from "../models/SeaQAComment";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { SEA_QA } from "../config/constants";
import { LanguageService } from "./language.service";
import { EmailTemplateService } from "./emailTemplate.service";
import { NotificationLogService } from "./notificationLog.service";
import _ from 'lodash';

export class SeaQAService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private seaQADao: SeaQADao;
    private categoryDao: CategoryDao;
    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.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.seaQADao = new SeaQADao(options);
        this.categoryDao = new CategoryDao(options);
        this.languageService = new LanguageService(options);
    }

    // --- Private Helpers ---

    /**
     * Safely increment or decrement a UserProfile counter column for a given userId.
     * @param userId  The user whose profile to update.
     * @param field   The camelCase field name matching the Sequelize model (e.g. 'questionCount').
     * @param delta   +1 to increment, -1 to decrement.
     * @param transaction Optional Sequelize transaction.
     */
    private async updateUserProfileCount(userId: number, field: 'questionCount' | 'answerCount' | 'answerCommentCount' | 'postCommentCount' | 'seaQALikesCount', delta: 1 | -1, transaction?: any): Promise<void> {
        if (!userId) return;
        if (delta === 1) {
            await Models.UserProfile.increment(field, { where: { userId }, transaction });
        } else {
            // Never go below 0
            await Models.UserProfile.decrement(field, { where: { userId, [field]: { [Op.gt]: 0 } }, transaction });
        }
    }

    private async ensureNotificationEmailTemplatesExist(): Promise<void> {
        const templates = [
            {
                code: 'seaqa-question-approved-email',
                title: 'SeaQA Question Approved',
                subject: 'Your SeaQA question has been approved!',
                body: '<p>Hi {{name}},</p><p>Great news! Your question "<strong>{{questionTitle}}</strong>" has been approved.</p><p><a href="{{targetLink}}">Click here to view it.</a></p>'
            },
            {
                code: 'seaqa-question-rejected-email',
                title: 'SeaQA Question Rejected',
                subject: 'Update on your SeaQA question',
                body: '<p>Hi {{name}},</p><p>Your question "<strong>{{questionTitle}}</strong>" has been rejected.</p><p>Reason: {{rejectionReason}}</p>'
            },
            {
                code: 'seaqa-answer-approved-email',
                title: 'SeaQA Answer Approved',
                subject: 'Your SeaQA answer has been approved!',
                body: '<p>Hi {{name}},</p><p>Great news! Your answer to the question "<strong>{{questionTitle}}</strong>" has been approved.</p><p><a href="{{targetLink}}">Click here to view it.</a></p>'
            },
            {
                code: 'seaqa-answer-rejected-email',
                title: 'SeaQA Answer Rejected',
                subject: 'Update on your SeaQA answer',
                body: '<p>Hi {{name}},</p><p>Your answer to the question "<strong>{{questionTitle}}</strong>" has been rejected.</p><p>Reason: {{rejectionReason}}</p>'
            }
        ];

        const emailTemplateService = new EmailTemplateService(this.options);
        for (const t of templates) {
            try {
                const exists = await Models.EmailTemplate.findOne({
                    where: { code: t.code, isRevision: false }
                });
                if (!exists) {
                    await emailTemplateService.createEmailTemplate({
                        title: t.title,
                        code: t.code,
                        subject: t.subject,
                        body: t.body as any,
                        status: 1, // ACTIVE
                        emailTemplateAttachmentIds: null
                    });
                }
            } catch (err) {
                console.error(`Failed to ensure email template ${t.code} exists:`, err);
            }
        }
    }

    private async notifyAdmins(notificationCode: string, questionId: string, answerId?: string, title?: string, slug?: string, answerBody?: string): Promise<void> {
        try {
            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 adminUserIds = adminUsers.map((ur: any) => ur.userId);
            const uniqueAdminUserIds = ([...new Set(adminUserIds)] as number[]).filter(id => id !== this.userId);

            const promises = uniqueAdminUserIds.map((adminId: number) =>
                NotificationLogService.emitByCode({
                    userId: adminId,
                    notificationCode: notificationCode,
                    fromUserId: this.userId,
                    replacements: {
                        type: 'seaqa',
                        questionId,
                        title,
                        data: {
                            questionId,
                            answerId,
                            title,
                            code: slug,
                            answer: answerBody
                        }
                    }
                })
            );
            await Promise.allSettled(promises);
        } catch (err) {
            console.error(`Failed to notify admins for ${notificationCode}:`, err);
        }
    }

    private async sendAdminActionNotifications(
        type: 'question' | 'answer',
        isApproved: boolean,
        targetId: number,
        authorId: number,
        authorName: string,
        authorEmail: string | null,
        questionTitle: string,
        extraDetails: { rejectedReason?: string | null, questionId?: number, slug?: string, answerBody?: string } = {}
    ): Promise<void> {
        if (authorId && this.userId && authorId === this.userId) {
            return;
        }
        try {
            await this.ensureNotificationEmailTemplatesExist();

            const protocol = process.env.PROTOCOL || 'http';
            const host = process.env.APPLICATION_HOST || 'localhost:3011';

            // 1. Send Email Notification
            if (authorEmail) {
                const templateCode = type === 'question'
                    ? (isApproved ? 'seaqa-question-approved-email' : 'seaqa-question-rejected-email')
                    : (isApproved ? 'seaqa-answer-approved-email' : 'seaqa-answer-rejected-email');

                const targetQuestionId = type === 'question' ? targetId : extraDetails.questionId;
                const discussionBaseUrl = process.env.SEAQA_DISCUSSION_BASE_URL || `${protocol}://${host}/discussion`;
                const codeToUse = extraDetails.slug || targetQuestionId;
                const targetLink = type === 'question'
                    ? `${discussionBaseUrl}/${codeToUse}`
                    : `${discussionBaseUrl}/${codeToUse}#answer-${targetId}`;

                const emailTemplateService = new EmailTemplateService(this.options);
                try {
                    await emailTemplateService.sendEmailTemplate(
                        templateCode,
                        [authorEmail],
                        {
                            name: authorName || 'User',
                            questionTitle: questionTitle,
                            targetLink: targetLink,
                            rejectionReason: extraDetails.rejectedReason || 'No reason specified'
                        }
                    );
                } catch (emailErr) {
                    console.error(`Failed to send email to ${authorEmail} for template ${templateCode}:`, emailErr);
                }
            }

            // 2. Send Push Notification
            const devices = await Models.UserDevice.findAll({
                where: {
                    userId: authorId,
                    status: 1
                }
            });

            if (devices && devices.length > 0) {
                const targetQuestionId = type === 'question' ? targetId : extraDetails.questionId;
                const slug = extraDetails.slug || '';

                let pushTitle = '';
                let pushBody = '';
                let pushData: Record<string, any> = {};

                if (type === 'question') {
                    if (isApproved) {
                        pushTitle = 'Question Approved';
                        pushBody = `Your question "${questionTitle}" has been approved.`;
                        pushData = {
                            type: 'seaqa_question_approved',
                            questionId: String(targetQuestionId),
                            code: slug
                        };
                    } else {
                        pushTitle = 'Question Rejected';
                        pushBody = `Your question "${questionTitle}" has been rejected. Reason: ${extraDetails.rejectedReason || 'No reason specified'}`;
                        pushData = {
                            type: 'seaqa_question_rejected',
                            questionId: String(targetQuestionId),
                            code: slug,
                            rejectedReason: extraDetails.rejectedReason || ''
                        };
                    }
                } else {
                    if (isApproved) {
                        pushTitle = 'Answer Approved';
                        pushBody = `Your answer on question "${questionTitle}" has been approved.`;
                        pushData = {
                            type: 'seaqa_answer_approved',
                            questionId: String(targetQuestionId),
                            answerId: String(targetId)
                        };
                    } else {
                        pushTitle = 'Answer Rejected';
                        pushBody = `Your answer on question "${questionTitle}" has been rejected. Reason: ${extraDetails.rejectedReason || 'No reason specified'}`;
                        pushData = {
                            type: 'seaqa_answer_rejected',
                            questionId: String(targetQuestionId),
                            answerId: String(targetId),
                            rejectedReason: extraDetails.rejectedReason || ''
                        };
                    }
                }

                for (const device of devices) {
                    if (device.device) {
                        try {
                            await Common.sendFcmNotification(
                                device.device,
                                null,
                                { title: pushTitle, body: pushBody },
                                { title: pushTitle, body: pushBody },
                                pushData,
                                false
                            );
                        } catch (pushErr) {
                            console.error(`Failed to send push notification to device ${device.device}:`, pushErr);
                        }
                    }
                }
            }

            // 3. Socket & Log Notifications
            const notifCode = type === 'question'
                ? (isApproved ? 'question_approved' : 'question_rejected')
                : (isApproved ? 'answer_approved' : 'answer_rejected');
            const targetQuestionId = type === 'question' ? targetId : extraDetails.questionId;
            const slug = extraDetails.slug || '';

            try {
                await NotificationLogService.emitByCode({
                    userId: authorId,
                    notificationCode: notifCode,
                    fromUserId: this.userId,
                    replacements: {
                        type: 'seaqa',
                        questionId: String(targetQuestionId),
                        title: questionTitle,
                        data: {
                            questionId: String(targetQuestionId),
                            answerId: type === 'answer' ? String(targetId) : undefined,
                            title: questionTitle,
                            code: slug,
                            answer: extraDetails.answerBody,
                            status: isApproved ? 'APPROVED' : 'REJECTED',
                            rejectedReason: !isApproved ? extraDetails.rejectedReason : undefined
                        }
                    }
                });
            } catch (logErr) {
                console.error(`Failed to send ${notifCode} notification log for user ${authorId}:`, logErr);
            }

        } catch (err) {
            console.error('Error in sendAdminActionNotifications:', err);
        }
    }

    // --- Questions ---

    async askQuestion(data: SeaQAQuestionRequest): Promise<SeaQAQuestionResponse> {
        const transaction = await sequelize.transaction();
        try {
            const code = Common.slugify(data.title);
            const questionData: any = {
                title: data.title,
                code,
                description: data.description || '',
                userId: this.userId as number,
                accountId: this.accountId
            };

            const languages = await this.languageService.getRequestedAndDefaultLanguage();
            const question = await this.seaQADao.createQuestion(questionData, data.categoryIds, languages, transaction);
            await transaction.commit();

            // Trigger notification to admins
            this.notifyAdmins('admin_question_submitted', String(question.id), undefined, data.title, code).catch(err => {
                console.error(`Failed to trigger admin_question_submitted:`, err);
            });

            return await this.getQuestionDetail(question.id);
        } catch (err) {
            await transaction.rollback();
            throw err;
        }
    }

    async updateQuestion(id: number, data: SeaQAQuestionUpdateRequest, credentials: any): Promise<SeaQAQuestionResponse> {
        // 1. Fetch existing question
        const question = await this.seaQADao.getQuestionById(id);
        if (!question) {
            throw new AppError(404, 'QUESTION_NOT_FOUND', {});
        }

        // 2. Authorization check: must be admin/superadmin OR the author of the question
        const scope = credentials?.scope;
        const isAdmin = Array.isArray(scope) ? scope.includes('admin') || scope.includes('superadmin') : (scope === 'admin' || scope === 'superadmin');
        const isAuthor = question.userId === credentials?.userData?.userId;

        if (!isAdmin && !isAuthor) {
            throw new AppError(403, 'UNAUTHORIZED_ACCESS', {});
        }

        // 3. Update question
        const transaction = await sequelize.transaction();
        try {
            const updatePayload: any = {};
            if (data.title) {
                updatePayload.title = data.title;
                updatePayload.code = Common.slugify(data.title);
            }
            if (typeof data.description !== 'undefined') {
                updatePayload.description = data.description || '';
            }
            if (data.status) {
                updatePayload.status = data.status;
            }

            const languages = await this.languageService.getRequestedAndDefaultLanguage();
            await this.seaQADao.updateQuestion(id, updatePayload, languages, data.categoryIds, transaction);
            await transaction.commit();

            if (data.status === 'pending' && question.status !== 'pending') {
                this.notifyAdmins('admin_question_submitted', String(id), undefined, updatePayload.title || question.title, updatePayload.code || question.code).catch(err => {
                    console.error(`Failed to trigger admin_question_submitted on update:`, err);
                });
            }

            return await this.getQuestionDetail(id);
        } catch (err) {
            await transaction.rollback();
            throw err;
        }
    }

    async deleteDraftQuestion(id: number): Promise<void> {
        const question = await Models.SeaQAQuestion.findByPk(id);
        if (!question) {
            throw new AppError(404, 'QUESTION_NOT_FOUND');
        }
        if (question.userId !== this.userId) {
            throw new AppError(403, 'FORBIDDEN_NOT_AUTHOR');
        }
        if (question.status !== SEA_QA.QUESTION_STATUS.PENDING) {
            throw new AppError(400, 'ONLY_PENDING_QUESTIONS_CAN_BE_DELETED');
        }
        await this.seaQADao.deleteQuestion(id);
    }

    async getQuestionDetail(id: number): Promise<SeaQAQuestionResponse> {
        const question = await this.seaQADao.getQuestionById(id);

        // Increment views
        await Models.SeaQAQuestion.increment('views', { where: { id } });

        return question;
    }

    async listQuestions(filters: SeaQAQuestionListRequest): Promise<SeaQAQuestionPaginatedData> {
        const result = await this.seaQADao.getQuestionList(filters);
        const totalPages = Common.getTotalPages(result.count, filters.perPage);
        return {
            data: result.rows,
            page: filters.page,
            perPage: filters.perPage,
            totalRecords: result.count,
            totalPages: totalPages
        };
    }

    async adminQuestionsAction(action: SeaQAQuestionAdminActionRequest): Promise<void> {
        const transaction = await sequelize.transaction();
        try {
            // Fetch existing statuses before updating (to detect transitions to/from 'approved')
            const existingQuestions = await Models.SeaQAQuestion.findAll({
                where: { id: action.ids },
                attributes: ['id', 'userId', 'status', 'code'],
                include: [
                    {
                        model: Models.User,
                        as: 'author',
                        attributes: ['id', 'email'],
                        include: [
                            {
                                model: Models.UserProfile,
                                as: 'userProfile',
                                attributes: ['name']
                            }
                        ]
                    },
                    {
                        model: Models.SeaQAQuestionContent,
                        as: 'content',
                        required: false,
                        include: [{ model: Models.Language, as: 'language', where: { code: this.language } }]
                    },
                    {
                        model: Models.SeaQAQuestionContent,
                        as: 'defaultContent',
                        required: false,
                        include: [{ model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
                    }
                ],
                transaction
            });

            const updateData: Partial<SeaQAQuestionAttributes> = {
                status: action.status as any,
                rejectedReason: action.rejectedReason,
                mergedIntoQuestionId: action.mergedIntoQuestionId,
                approvedByAdminId: this.userId,
                approvedAt: action.status === SEA_QA.QUESTION_STATUS.APPROVED ? new Date() : null
            };

            await this.seaQADao.updateQuestionsBulk(action.ids, updateData, transaction);

            // Adjust questionCount for each affected author based on status transition
            const newStatus = action.status;
            const approvedStatus = SEA_QA.QUESTION_STATUS.APPROVED;
            for (const q of existingQuestions) {
                const wasApproved = (q as any).status === approvedStatus;
                const nowApproved = newStatus === approvedStatus;
                if (!wasApproved && nowApproved) {
                    // Newly approved → increment author's questionCount
                    await this.updateUserProfileCount((q as any).userId, 'questionCount', 1, transaction);
                } else if (wasApproved && !nowApproved) {
                    // Revoked from approved → decrement author's questionCount
                    await this.updateUserProfileCount((q as any).userId, 'questionCount', -1, transaction);
                }
            }

            await transaction.commit();

            // Trigger Notifications asynchronously after transaction commits successfully
            for (const q of existingQuestions) {
                const oldStatus = (q as any).status;
                const wasApproved = oldStatus === SEA_QA.QUESTION_STATUS.APPROVED;
                const nowApproved = newStatus === SEA_QA.QUESTION_STATUS.APPROVED;
                const wasRejected = oldStatus === SEA_QA.QUESTION_STATUS.REJECTED;
                const nowRejected = newStatus === SEA_QA.QUESTION_STATUS.REJECTED;

                let shouldNotify = false;
                let isApprovedNotification = false;

                if (!wasApproved && nowApproved) {
                    shouldNotify = true;
                    isApprovedNotification = true;
                } else if (!wasRejected && nowRejected) {
                    shouldNotify = true;
                    isApprovedNotification = false;
                }

                if (shouldNotify) {
                    const author = (q as any).author;
                    const authorName = author?.userProfile?.name || 'User';
                    const authorEmail = author?.email || null;
                    const title = (q as any).content?.title || (q as any).defaultContent?.title || 'No Title Available';

                    this.sendAdminActionNotifications(
                        'question',
                        isApprovedNotification,
                        q.id,
                        (q as any).userId,
                        authorName,
                        authorEmail,
                        title,
                        {
                            rejectedReason: action.rejectedReason,
                            slug: (q as any).code
                        }
                    ).catch(err => {
                        console.error(`Failed to send question notification for ID ${q.id}:`, err);
                    });
                }
            }
        } catch (err) {
            await transaction.rollback();
            throw err;
        }
    }

    async markAsGoodQuestion(id: number, isGood: boolean): Promise<void> {
        await this.seaQADao.updateQuestion(id, { isGoodQuestion: isGood });

        if (isGood) {
            try {
                const question = await this.seaQADao.getQuestionById(id);
                if (question) {
                    await NotificationLogService.emitByCode({
                        userId: question.userId,
                        notificationCode: 'question_good',
                        fromUserId: this.userId,
                        replacements: {
                            type: 'seaqa',
                            data: {
                                questionId: String(id),
                                title: (question as any).content?.title || (question as any).defaultContent?.title || '',
                                code: question.code
                            }
                        }
                    });
                }
            } catch (err) {
                console.error(`Failed to send question_good notification for question ${id}:`, err);
            }
        }
    }

    // --- Answers ---

    async postAnswer(questionId: number, data: SeaQAAnswerRequest): Promise<SeaQAAnswerResponse> {
        const question = await Models.SeaQAQuestion.findByPk(questionId, {
            include: [
                {
                    model: Models.SeaQAQuestionContent,
                    as: 'content',
                    where: { languageId: 1 },
                    required: false
                },
                {
                    model: Models.SeaQAQuestionContent,
                    as: 'defaultContent',
                    where: { languageId: process.env.DEFAULT_LANGUAGE_ID || 1 },
                    required: false
                }
            ]
        });
        if (!question) throw new AppError(404, 'QUESTION_NOT_FOUND', {});

        const questionTitle = (question as any)?.content?.title || (question as any)?.defaultContent?.title;
        if (!questionTitle || questionTitle.trim() === '') {
            throw new AppError(400, 'QUESTION_TITLE_EMPTY', { message: 'Question title cannot be empty' });
        }
        if (question.status !== SEA_QA.QUESTION_STATUS.APPROVED) {
            throw new AppError(400, 'QUESTION_NOT_APPROVED', {});
        }

        const existingAnswer = await Models.SeaQAAnswer.findOne({
            where: { questionId, userId: this.userId },
            order: [['createdAt', 'DESC']]
        });
        if (existingAnswer && existingAnswer.status !== SEA_QA.ANSWER_STATUS.REJECTED) {
            throw new AppError(400, 'USER_ALREADY_ANSWERED', {});
        }

        const wordCount = data.body ? data.body.trim().split(/\s+/).filter(Boolean).length : 0;
        const answerData: any = {
            questionId,
            body: data.body,
            status: data.status as any,
            userId: this.userId as number,
            wordCount
        };
        const languages = await this.languageService.getRequestedAndDefaultLanguage();
        const answer = await this.seaQADao.createAnswer(answerData, languages);
        if (data.status === SEA_QA.ANSWER_STATUS.PUBLISHED) {
            await Models.SeaQAQuestion.increment('answersCount', { where: { id: questionId } });
            // Increment author's answerCount
            if (this.userId) {
                await this.updateUserProfileCount(this.userId, 'answerCount', 1);
            }
        } else if (data.status === SEA_QA.ANSWER_STATUS.PENDING_APPROVAL) {
            this.notifyAdmins('admin_answer_submitted', String(questionId), String(answer.id), questionTitle, question.slug, (answer as any).body || data.body).catch(err => {
                console.error(`Failed to trigger admin_answer_submitted:`, err);
            });
        }
        return await this.seaQADao.getAnswerById(answer.id);
    }

    async updateAnswer(id: number, data: SeaQAAnswerUpdateRequest): Promise<SeaQAAnswerResponse> {
        const answer = await Models.SeaQAAnswer.findByPk(id);
        if (!answer) throw new AppError(404, 'ANSWER_NOT_FOUND', {});
        const scope = this.options.scope;
        const isAdmin = Array.isArray(scope)
            ? scope.includes('admin') || scope.includes('superadmin')
            : scope === 'admin' || scope === 'superadmin';
        if (!isAdmin && answer.userId !== this.userId) throw new AppError(403, 'NOT_AUTHORIZED', {});

        const question = await Models.SeaQAQuestion.findByPk(answer.questionId, {
            include: [
                {
                    model: Models.SeaQAQuestionContent,
                    as: 'content',
                    where: { languageId: 1 },
                    required: false
                },
                {
                    model: Models.SeaQAQuestionContent,
                    as: 'defaultContent',
                    where: { languageId: process.env.DEFAULT_LANGUAGE_ID || 1 },
                    required: false
                }
            ]
        });
        if (!question) throw new AppError(404, 'QUESTION_NOT_FOUND', {});

        const questionTitle = (question as any)?.content?.title || (question as any)?.defaultContent?.title;
        if (!questionTitle || questionTitle.trim() === '') {
            throw new AppError(400, 'QUESTION_TITLE_EMPTY', { message: 'Question title cannot be empty' });
        }
        if (question.status !== SEA_QA.QUESTION_STATUS.APPROVED) {
            throw new AppError(400, 'QUESTION_NOT_APPROVED', {});
        }

        const updateData: any = {};
        if (typeof data.body !== 'undefined') {
            updateData.body = data.body;
            updateData.wordCount = data.body ? data.body.trim().split(/\s+/).filter(Boolean).length : 0;
        }
        if (typeof data.status !== 'undefined') {
            updateData.status = data.status;
        }
        if (Object.keys(updateData).length === 0) {
            return await this.seaQADao.getAnswerById(id);
        }
        updateData.editedAt = new Date();

        const languages = await this.languageService.getRequestedAndDefaultLanguage();
        await this.seaQADao.updateAnswer(id, updateData, languages);

        if (data.status === SEA_QA.ANSWER_STATUS.PENDING_APPROVAL && answer.status !== SEA_QA.ANSWER_STATUS.PENDING_APPROVAL) {
            this.notifyAdmins('admin_answer_submitted', String(answer.questionId), String(id), questionTitle, question.slug, updateData.body || answer.body).catch(err => {
                console.error(`Failed to trigger admin_answer_submitted on update:`, err);
            });
        }

        return await this.seaQADao.getAnswerById(id);
    }

    async deleteDraftAnswer(id: number): Promise<void> {
        const answer = await Models.SeaQAAnswer.findByPk(id);
        if (!answer) {
            throw new AppError(404, 'ANSWER_NOT_FOUND');
        }
        if (answer.userId !== this.userId) {
            throw new AppError(403, 'FORBIDDEN_NOT_AUTHOR');
        }
        if (answer.status !== SEA_QA.ANSWER_STATUS.DRAFT && answer.status !== SEA_QA.ANSWER_STATUS.PENDING_APPROVAL) {
            throw new AppError(400, 'ONLY_DRAFT_ANSWERS_CAN_BE_DELETED');
        }
        await this.seaQADao.deleteAnswer(id);
    }

    async adminAnswersAction(action: SeaQAAnswerAdminActionRequest): Promise<void> {
        const transaction = await sequelize.transaction();
        try {
            const updateData: Partial<SeaQAAnswerAttributes> = {
                status: action.status === 'published' ? SEA_QA.ANSWER_STATUS.PUBLISHED as any : 'rejected',
                rejectedReason: action.status === 'rejected' ? action.rejectedReason ?? null : null,
                requiresApproval: false
            };

            // Fetch existing answers before updating (to detect status transitions and get notification details)
            const existingAnswers = await Models.SeaQAAnswer.findAll({
                where: { id: action.ids },
                attributes: ['id', 'userId', 'status', 'questionId'],
                include: [
                    {
                        model: Models.SeaQAAnswerContent,
                        as: 'content',
                        required: false,
                        include: [{ model: Models.Language, as: 'language', where: { code: this.language } }]
                    },
                    {
                        model: Models.SeaQAAnswerContent,
                        as: 'defaultContent',
                        required: false,
                        include: [{ model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
                    },
                    {
                        model: Models.User,
                        as: 'author',
                        attributes: ['id', 'email'],
                        include: [
                            {
                                model: Models.UserProfile,
                                as: 'userProfile',
                                attributes: ['name']
                            }
                        ]
                    },
                    {
                        model: Models.SeaQAQuestion,
                        as: 'question',
                        attributes: ['id', 'code'],
                        include: [
                            {
                                model: Models.SeaQAQuestionContent,
                                as: 'content',
                                required: false,
                                include: [{ model: Models.Language, as: 'language', where: { code: this.language } }]
                            },
                            {
                                model: Models.SeaQAQuestionContent,
                                as: 'defaultContent',
                                required: false,
                                include: [{ model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
                            }
                        ]
                    }
                ],
                transaction
            });

            await this.seaQADao.updateAnswersBulk(action.ids, updateData, transaction);

            // Recalculate answersCount for all unique affected questions
            const questionIds = Array.from(new Set(existingAnswers.map((a: any) => a.questionId)));
            for (const qId of questionIds) {
                const count = await Models.SeaQAAnswer.count({
                    where: { questionId: qId, status: SEA_QA.ANSWER_STATUS.PUBLISHED },
                    transaction
                });
                await Models.SeaQAQuestion.update({ answersCount: count }, { where: { id: qId }, transaction });
            }

            // Adjust answerCount on UserProfile for each affected author
            const newStatus = action.status;
            const publishedStatus = SEA_QA.ANSWER_STATUS.PUBLISHED;
            for (const ans of existingAnswers) {
                const wasPublished = (ans as any).status === publishedStatus;
                const nowPublished = newStatus === 'published';
                if (!wasPublished && nowPublished) {
                    await this.updateUserProfileCount((ans as any).userId, 'answerCount', 1, transaction);
                } else if (wasPublished && !nowPublished) {
                    await this.updateUserProfileCount((ans as any).userId, 'answerCount', -1, transaction);
                }
            }

            await transaction.commit();

            // Trigger Notifications asynchronously after transaction commits successfully
            const targetStatus = action.status;
            for (const ans of existingAnswers) {
                const oldStatus = (ans as any).status;
                const wasPublished = oldStatus === SEA_QA.ANSWER_STATUS.PUBLISHED;
                const nowPublished = targetStatus === 'published';
                const wasRejected = oldStatus === 'rejected';
                const nowRejected = targetStatus === 'rejected';

                let shouldNotify = false;
                let isApprovedNotification = false;

                if (!wasPublished && nowPublished) {
                    shouldNotify = true;
                    isApprovedNotification = true;
                } else if (!wasRejected && nowRejected) {
                    shouldNotify = true;
                    isApprovedNotification = false;
                }

                if (shouldNotify) {
                    const author = (ans as any).author;
                    const authorName = author?.userProfile?.name || 'User';
                    const authorEmail = author?.email || null;

                    const question = (ans as any).question;
                    const questionTitle = question?.content?.title || question?.defaultContent?.title || 'No Title Available';

                    this.sendAdminActionNotifications(
                        'answer',
                        isApprovedNotification,
                        ans.id,
                        (ans as any).userId,
                        authorName,
                        authorEmail,
                        questionTitle,
                        {
                            rejectedReason: action.rejectedReason,
                            questionId: (ans as any).questionId,
                            slug: question?.code,
                            answerBody: (ans as any).content?.body || (ans as any).defaultContent?.body || ''
                        }
                    ).catch(err => {
                        console.error(`Failed to send answer notification for ID ${ans.id}:`, err);
                    });
                }
            }
        } catch (err) {
            await transaction.rollback();
            throw err;
        }
    }

    async markAsFeaturedAnswer(id: number, isFeatured: boolean): Promise<void> {
        const updateData: Partial<SeaQAAnswerAttributes> = {
            isFeatured,
            featuredAt: isFeatured ? new Date() : null
        };
        await this.seaQADao.updateAnswer(id, updateData);

        if (isFeatured) {
            try {
                const answer = await this.seaQADao.getAnswerById(id);
                if (answer) {
                    const question = await this.seaQADao.getQuestionById(answer.questionId);
                    if (question) {
                        await NotificationLogService.emitByCode({
                            userId: answer.userId,
                            notificationCode: 'answer_featured',
                            fromUserId: this.userId,
                            replacements: {
                                type: 'seaqa',
                                questionId: String(answer.questionId),
                                title: (question as any).content?.title || (question as any).defaultContent?.title || '',
                                data: {
                                    questionId: String(answer.questionId),
                                    answerId: String(id),
                                    title: (question as any).content?.title || (question as any).defaultContent?.title || '',
                                    code: question.code,
                                    answer: (answer as any).body
                                }
                            }
                        });
                    }
                }
            } catch (err) {
                console.error(`Failed to send answer_featured notification for answer ${id}:`, err);
            }
        }
    }

    // --- Comments ---

    async postComment(answerId: number, data: SeaQACommentRequest): Promise<SeaQACommentResponse> {
        const commentData: SeaQACommentCreationAttributes = {
            answerId,
            content: data.content,
            parentId: data.parentId || null,
            userId: this.userId as number
        };
        const comment = await this.seaQADao.createComment(commentData);
        // Increment the author's answerCommentCount
        if (this.userId) {
            await this.updateUserProfileCount(this.userId, 'answerCommentCount', 1);
        }
        return await this.seaQADao.getCommentById(comment.id);
    }

    async getComments(answerId: number, filters?: any): Promise<SeaQACommentResponse[]> {
        return await this.seaQADao.getCommentsByAnswerId(answerId, filters);
    }

    async listComments(filters: SeaQACommentGlobalListRequest): Promise<SeaQACommentPaginatedData> {
        const { count, rows } = await this.seaQADao.getCommentList(filters);
        return {
            data: rows,
            page: filters.page,
            perPage: filters.perPage,
            totalRecords: count,
            totalPages: Math.ceil(count / filters.perPage)
        };
    }

    // --- Bookmarks ---

    async toggleBookmark(entityId: number, entityType: 'question' | 'answer'): Promise<boolean> {
        return await this.seaQADao.toggleBookmark(entityId, entityType);
    }

    async listBookmarks(userId: number, entityType: 'question' | 'answer', page: number, perPage: number, sortBy?: string, sortDirection?: 'asc' | 'desc'): Promise<any> {
        if (entityType === 'question') {
            const filters: SeaQAQuestionListRequest = {
                page, perPage, bookmarkedByUserId: userId, sortBy, sortDirection
            };
            const { count, rows } = await this.seaQADao.getQuestionList(filters);
            return {
                data: rows,
                page,
                perPage,
                totalRecords: count,
                totalPages: Math.ceil(count / perPage)
            };
        } else {
            const filters: SeaQAAnswerListRequest = {
                page, perPage, bookmarkedByUserId: userId, includeQuestion: true, sortBy, sortDirection
            };
            const { count, rows } = await this.seaQADao.getAnswerList(filters);
            return {
                data: rows,
                page,
                perPage,
                totalRecords: count,
                totalPages: Math.ceil(count / perPage)
            };
        }
    }

    // --- Likes ---

    async toggleLike(entityId: number, entityType: 'question' | 'answer'): Promise<boolean> {
        return await this.seaQADao.toggleLike(entityId, entityType);
    }

    async unlike(entityId: number, entityType: 'question' | 'answer'): Promise<boolean> {
        return await this.seaQADao.unlike(entityId, entityType);
    }

    // --- Topics/Follow ---

    async followTopic(categoryId: number): Promise<void> {
        await this.seaQADao.followTopic(categoryId);
    }

    async unfollowTopic(categoryId: number): Promise<void> {
        await this.seaQADao.unfollowTopic(categoryId);
    }

    async getFollowedTopics(): Promise<CategoryObjectSummaryInteface[]> {
        return await this.seaQADao.getFollowedTopics();
    }

    async updateCategoryApplicability(categoryId: number, data: CategoryApplicabilityRequest): Promise<void> {
        await this.seaQADao.updateApplicability(categoryId, [data.applicableRankId], [data.applicableShipTypeId], data.applicableTo);
    }

    async getAnswerList(filters: SeaQAAnswerListRequest): Promise<SeaQAAnswerPaginatedData> {
        const { count, rows } = await this.seaQADao.getAnswerList(filters);
        return {
            data: rows,
            page: filters.page,
            perPage: filters.perPage,
            totalRecords: count,
            totalPages: Math.ceil(count / filters.perPage)
        };
    }

    async getQuestionListAll(filters: SeaQAQuestionListRequest): Promise<SeaQAQuestionSimplePaginatedData> {
        const { count, rows } = await this.seaQADao.getQuestionListAll(filters);
        return {
            data: rows,
            page: filters.page,
            perPage: filters.perPage,
            totalRecords: count,
            totalPages: Math.ceil(count / filters.perPage)
        };
    }

    async listPublicQuestions(filters: SeaQAQuestionListRequest): Promise<SeaQAQuestionPaginatedData> {
        const result = await this.seaQADao.getPublicQuestionList(filters);
        const totalPages = Common.getTotalPages(result.count, filters.perPage);
        return {
            data: result.rows,
            page: filters.page,
            perPage: filters.perPage,
            totalRecords: result.count,
            totalPages: totalPages
        };
    }

    async getPublicQuestionDetail(code: string): Promise<SeaQAQuestionResponse> {
        const questionObj = await Models.SeaQAQuestion.findOne({ where: { code, status: { [Op.in]: [SEA_QA.QUESTION_STATUS.APPROVED, SEA_QA.QUESTION_STATUS.CLOSED] } } });
        if (!questionObj) {
            throw new AppError(404, 'QUESTION_NOT_FOUND', {});
        }

        const id = questionObj.id;
        const question = await this.seaQADao.getQuestionById(id);

        if (this.userId) {
            const myAnswer = await this.seaQADao.getUserAnswerForQuestion(id, this.userId);
            if (myAnswer && (myAnswer.status === SEA_QA.ANSWER_STATUS.DRAFT || myAnswer.status === SEA_QA.ANSWER_STATUS.PENDING_APPROVAL)) {
                const comments = await this.seaQADao.getCommentsByAnswerId(myAnswer.id as number);
                myAnswer.comments = comments.slice(0, 5);
                question.myAnswer = myAnswer;
            } else {
                question.myAnswer = null;
            }

            const answers = await this.seaQADao.getAnswersByQuestionId(id);
            const filteredAnswers = answers.filter(ans => ans.userId !== this.userId || ans.status === SEA_QA.ANSWER_STATUS.PUBLISHED);
            for (const ans of filteredAnswers) {
                const comments = await this.seaQADao.getCommentsByAnswerId(ans.id as number);
                ans.comments = comments.slice(0, 5);
            }
            question.answers = filteredAnswers;
        } else {
            question.myAnswer = null;
            question.answers = [];
        }

        // Increment views only for simple users
        const scope = this.options?.scope;
        const isAdmin = Array.isArray(scope) ? scope.includes('admin') || scope.includes('superadmin') : scope === 'admin' || scope === 'superadmin';
        const isUser = Array.isArray(scope) ? scope.includes('user') : scope === 'user';

        if (isUser && !isAdmin) {
            await Models.SeaQAQuestion.increment('views', { where: { id } });
        }

        return question;
    }

    async requestAnswer(questionId: number, userIds: number[]): Promise<{ success: boolean; message: string }> {
        const isAdmin = this.options.scope?.includes('admin') || this.options.scope?.includes('superadmin') || false;
        if (!isAdmin) {
            throw new AppError(403, 'PERMISSION_DENIED', {});
        }

        const question = await this.seaQADao.getQuestionById(questionId);
        if (!question) {
            throw new AppError(404, 'QUESTION_NOT_FOUND', {});
        }

        const title = question.title || "No Title Available";

        // Dynamically ensure request-answer-email template exists
        let templateExists = await Models.EmailTemplate.findOne({ where: { code: 'request-answer-email' } });
        if (!templateExists) {
            const transaction = await sequelize.transaction();
            try {
                const emailTemplate = await Models.EmailTemplate.create({
                    code: 'request-answer-email',
                    status: 1,
                    replacements: 'name,questionTitle,questionUrl'
                }, { transaction });

                const defaultLang = await Models.Language.findOne({ where: { isDefault: true }, transaction });
                const languageId = defaultLang ? defaultLang.id : 1;

                await Models.EmailTemplateContent.create({
                    emailTemplateId: emailTemplate.id,
                    languageId: languageId,
                    title: 'Request Answer on Question',
                    subject: 'Request to answer: {{questionTitle}}',
                    body: 'Hello {{name}},<br/><br/>You have been requested to answer the question: <b>{{questionTitle}}</b>.<br/><br/>Click <a href="{{questionUrl}}">here</a> to view and answer the question.',
                    bodyText: 'Hello {{name}},\n\nYou have been requested to answer the question: {{questionTitle}}.\n\nClick {{questionUrl}} to view and answer the question.'
                }, { transaction });

                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                console.error("Error creating email template request-answer-email:", err);
            }
        }

        const users = await Models.User.findAll({
            where: {
                id: userIds,
                status: 1
            },
            include: [
                {
                    model: Models.UserProfile,
                    as: 'userProfile',
                    attributes: ['name']
                }
            ]
        });

        if (!users || users.length === 0) {
            throw new AppError(404, 'NO_ACTIVE_USERS_FOUND', {});
        }

        const emailTemplateService = new EmailTemplateService(this.options);
        const protocol = process.env.PROTOCOL || 'http';
        const host = process.env.APPLICATION_HOST || 'localhost:3011';
        const discussionBaseUrl = process.env.SEAQA_DISCUSSION_BASE_URL || `${protocol}://${host}/discussion`;
        const questionUrl = `${discussionBaseUrl}/${question.code}`;

        // Send email and log notification for each user
        for (const user of users) {
            if (user.email) {
                try {
                    await emailTemplateService.sendEmailTemplate(
                        'request-answer-email',
                        [user.email],
                        {
                            name: user.userProfile?.name || user.username || 'User',
                            questionTitle: title,
                            questionUrl: questionUrl
                        }
                    );
                } catch (emailErr) {
                    console.error(`Failed to send email to ${user.email}:`, emailErr);
                }
            }

            try {
                await NotificationLogService.emitByCode({
                    userId: user.id,
                    notificationCode: 'request_answer',
                    fromUserId: this.userId,
                    replacements: {
                        type: 'seaqa',
                        questionId: String(questionId),
                        title: title,
                        data: {
                            questionId: String(questionId),
                            title: title,
                            code: question.code || (question as any).slug
                        }
                    }
                });
            } catch (logErr) {
                console.error(`Failed to send request_answer notification log for user ${user.id}:`, logErr);
            }
        }

        // Send push notification to each user's active device
        const devices = await Models.UserDevice.findAll({
            where: {
                userId: userIds,
                status: 1
            }
        });

        for (const device of devices) {
            if (device.device) {
                try {
                    await Common.sendFcmNotification(
                        device.device,
                        null,
                        {
                            title: "Answer Requested",
                            body: `An admin has requested your answer on question: ${title}`
                        },
                        {
                            title: "Answer Requested",
                            body: `An admin has requested your answer on question: ${title}`
                        },
                        {
                            questionId: questionId,
                            code: question.code
                        },
                        false
                    );
                } catch (pushErr) {
                    console.error(`Failed to send push notification to device ${device.device}:`, pushErr);
                }
            }
        }

        return {
            success: true,
            message: "Answer request sent successfully by email and notification to all selected users."
        };
    }
}
