"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.campaignsService = void 0;
const client_1 = require("@prisma/client");
const database_1 = require("../../../config/database");
const error_handler_1 = require("../../../middleware/error-handler");
const roles_1 = require("../../../utils/roles");
function escapeHtml(unsafe) {
    return unsafe
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}
const campaignSelect = {
    id: true,
    slug: true,
    title: true,
    description: true,
    emailSubject: true,
    emailBody: true,
    callToAction: true,
    coverPhoto: true,
    coverVideoId: true,
    status: true,
    allowSmtpEmail: true,
    allowMailtoLink: true,
    collectUserInfo: true,
    showEmailCount: true,
    showCallCount: true,
    allowEmailEditing: true,
    allowCustomRecipients: true,
    showResponseWall: true,
    highlightCampaign: true,
    targetGovernmentLevels: true,
    createdByUserId: true,
    createdByUserEmail: true,
    createdByUserName: true,
    isUserGenerated: true,
    moderationStatus: true,
    reviewedByUserId: true,
    reviewedAt: true,
    rejectionReason: true,
    moderationNotes: true,
    createdAt: true,
    updatedAt: true,
    _count: {
        select: {
            emails: true,
            responses: true,
        },
    },
};
/** Public-facing select — strips admin-only fields (emails, internal IDs, moderation notes) */
const publicCampaignSelect = {
    id: true,
    slug: true,
    title: true,
    description: true,
    emailSubject: true,
    emailBody: true,
    callToAction: true,
    coverPhoto: true,
    coverVideoId: true,
    status: true,
    allowSmtpEmail: true,
    allowMailtoLink: true,
    collectUserInfo: true,
    showEmailCount: true,
    showCallCount: true,
    allowEmailEditing: true,
    allowCustomRecipients: true,
    showResponseWall: true,
    highlightCampaign: true,
    targetGovernmentLevels: true,
    createdByUserName: true,
    isUserGenerated: true,
    moderationStatus: true,
    createdAt: true,
    updatedAt: true,
    _count: {
        select: {
            emails: true,
            responses: true,
        },
    },
};
function generateSlug(title) {
    return title
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 80);
}
async function resolveSlugCollision(slug, excludeId) {
    let candidate = slug;
    let suffix = 2;
    while (true) {
        const existing = await database_1.prisma.campaign.findUnique({
            where: { slug: candidate },
            select: { id: true },
        });
        if (!existing || (excludeId && existing.id === excludeId)) {
            return candidate;
        }
        candidate = `${slug}-${suffix}`;
        suffix++;
    }
}
exports.campaignsService = {
    async findAll(filters, user) {
        const { page, limit, search, status } = filters;
        const skip = (page - 1) * limit;
        const where = {};
        if (search) {
            where.OR = [
                { title: { contains: search, mode: 'insensitive' } },
                { description: { contains: search, mode: 'insensitive' } },
            ];
        }
        if (status)
            where.status = status;
        // Non-admin users only see their own campaigns
        if (user && !(0, roles_1.hasAnyRole)(user, roles_1.ADMIN_ROLES)) {
            where.createdByUserId = user.id;
        }
        const [campaigns, total] = await Promise.all([
            database_1.prisma.campaign.findMany({
                where,
                select: campaignSelect,
                skip,
                take: limit,
                orderBy: { createdAt: 'desc' },
            }),
            database_1.prisma.campaign.count({ where }),
        ]);
        return {
            campaigns,
            pagination: {
                page,
                limit,
                total,
                totalPages: Math.ceil(total / limit),
            },
        };
    },
    async findById(id) {
        const campaign = await database_1.prisma.campaign.findUnique({
            where: { id },
            select: campaignSelect,
        });
        if (!campaign) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        return campaign;
    },
    async findBySlug(slug) {
        const campaign = await database_1.prisma.campaign.findUnique({
            where: { slug },
            select: campaignSelect,
        });
        if (!campaign) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        return campaign;
    },
    async create(data, user) {
        const baseSlug = generateSlug(data.title);
        const slug = await resolveSlugCollision(baseSlug);
        // Look up user name from DB
        const dbUser = await database_1.prisma.user.findUnique({
            where: { id: user.id },
            select: { name: true },
        });
        // If highlighting this campaign, unset any other highlighted campaign
        if (data.highlightCampaign) {
            await database_1.prisma.campaign.updateMany({
                where: { highlightCampaign: true },
                data: { highlightCampaign: false },
            });
        }
        const campaign = await database_1.prisma.campaign.create({
            data: {
                ...data,
                slug,
                createdByUserId: user.id,
                createdByUserEmail: user.email,
                createdByUserName: dbUser?.name ?? null,
            },
            select: campaignSelect,
        });
        return campaign;
    },
    async update(id, data) {
        const existing = await database_1.prisma.campaign.findUnique({ where: { id } });
        if (!existing) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        const updateData = { ...data };
        // Regenerate slug if title changes
        if (data.title && data.title !== existing.title) {
            const baseSlug = generateSlug(data.title);
            updateData.slug = await resolveSlugCollision(baseSlug, id);
        }
        // If highlighting this campaign, unset any other highlighted campaign
        if (data.highlightCampaign) {
            await database_1.prisma.campaign.updateMany({
                where: { highlightCampaign: true, id: { not: id } },
                data: { highlightCampaign: false },
            });
        }
        const campaign = await database_1.prisma.campaign.update({
            where: { id },
            data: updateData,
            select: campaignSelect,
        });
        return campaign;
    },
    async findActiveCampaigns() {
        return database_1.prisma.campaign.findMany({
            where: { status: 'ACTIVE' },
            select: publicCampaignSelect,
            orderBy: [
                { highlightCampaign: 'desc' },
                { createdAt: 'desc' },
            ],
        });
    },
    async findBySlugPublic(slug) {
        const campaign = await database_1.prisma.campaign.findUnique({
            where: { slug },
            select: publicCampaignSelect,
        });
        if (!campaign) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        if (campaign.status !== 'ACTIVE') {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        return campaign;
    },
    async delete(id) {
        const existing = await database_1.prisma.campaign.findUnique({ where: { id } });
        if (!existing) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        await database_1.prisma.campaign.delete({ where: { id } });
    },
    // --- User-Generated Campaign Methods ---
    async createUserCampaign(data, user) {
        const baseSlug = generateSlug(data.title);
        const slug = await resolveSlugCollision(baseSlug);
        const dbUser = await database_1.prisma.user.findUnique({
            where: { id: user.id },
            select: { name: true },
        });
        const campaign = await database_1.prisma.campaign.create({
            data: {
                slug,
                title: escapeHtml(data.title),
                description: data.description ? escapeHtml(data.description) : null,
                emailSubject: escapeHtml(data.emailSubject),
                emailBody: escapeHtml(data.emailBody),
                callToAction: data.callToAction ? escapeHtml(data.callToAction) : null,
                targetGovernmentLevels: data.targetGovernmentLevels,
                status: 'DRAFT',
                isUserGenerated: true,
                moderationStatus: client_1.CampaignModerationStatus.PENDING_REVIEW,
                allowSmtpEmail: false,
                allowMailtoLink: true,
                collectUserInfo: true,
                showEmailCount: true,
                showCallCount: false,
                allowEmailEditing: false,
                allowCustomRecipients: false,
                showResponseWall: false,
                highlightCampaign: false,
                createdByUserId: user.id,
                createdByUserEmail: user.email,
                createdByUserName: dbUser?.name ?? null,
            },
            select: campaignSelect,
        });
        return campaign;
    },
    async findUserCampaigns(userId) {
        return database_1.prisma.campaign.findMany({
            where: { createdByUserId: userId, isUserGenerated: true },
            select: campaignSelect,
            orderBy: { createdAt: 'desc' },
        });
    },
    async updateUserCampaign(id, data, user) {
        const existing = await database_1.prisma.campaign.findUnique({ where: { id } });
        if (!existing) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        if (existing.createdByUserId !== user.id) {
            throw new error_handler_1.AppError(403, 'You can only edit your own campaigns', 'FORBIDDEN');
        }
        if (!existing.isUserGenerated) {
            throw new error_handler_1.AppError(403, 'Cannot edit admin-created campaigns', 'FORBIDDEN');
        }
        if (existing.moderationStatus !== client_1.CampaignModerationStatus.CHANGES_REQUESTED &&
            existing.moderationStatus !== client_1.CampaignModerationStatus.PENDING_REVIEW) {
            throw new error_handler_1.AppError(400, 'Campaign cannot be edited in its current state', 'INVALID_STATE');
        }
        const updateData = {};
        if (data.title) {
            updateData.title = escapeHtml(data.title);
            const baseSlug = generateSlug(data.title);
            updateData.slug = await resolveSlugCollision(baseSlug, id);
        }
        if (data.description !== undefined)
            updateData.description = data.description ? escapeHtml(data.description) : null;
        if (data.emailSubject)
            updateData.emailSubject = escapeHtml(data.emailSubject);
        if (data.emailBody)
            updateData.emailBody = escapeHtml(data.emailBody);
        if (data.callToAction !== undefined)
            updateData.callToAction = data.callToAction ? escapeHtml(data.callToAction) : null;
        if (data.targetGovernmentLevels)
            updateData.targetGovernmentLevels = data.targetGovernmentLevels;
        // Reset to pending review on edit
        updateData.moderationStatus = client_1.CampaignModerationStatus.PENDING_REVIEW;
        updateData.rejectionReason = null;
        return database_1.prisma.campaign.update({
            where: { id },
            data: updateData,
            select: campaignSelect,
        });
    },
    // --- Moderation Methods ---
    async findModerationQueue(filters) {
        const { page, limit, search, moderationStatus } = filters;
        const skip = (page - 1) * limit;
        const where = { isUserGenerated: true };
        if (moderationStatus)
            where.moderationStatus = moderationStatus;
        if (search) {
            where.OR = [
                { title: { contains: search, mode: 'insensitive' } },
                { createdByUserName: { contains: search, mode: 'insensitive' } },
                { createdByUserEmail: { contains: search, mode: 'insensitive' } },
            ];
        }
        const [campaigns, total] = await Promise.all([
            database_1.prisma.campaign.findMany({
                where,
                select: campaignSelect,
                skip,
                take: limit,
                orderBy: { createdAt: 'desc' },
            }),
            database_1.prisma.campaign.count({ where }),
        ]);
        return {
            campaigns,
            pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
        };
    },
    async getModerationStats() {
        const [total, pending, approved, rejected, changesRequested] = await Promise.all([
            database_1.prisma.campaign.count({ where: { isUserGenerated: true } }),
            database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.PENDING_REVIEW } }),
            database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.APPROVED } }),
            database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.REJECTED } }),
            database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.CHANGES_REQUESTED } }),
        ]);
        return { total, pending, approved, rejected, changesRequested };
    },
    async moderateCampaign(id, input, reviewer) {
        const existing = await database_1.prisma.campaign.findUnique({ where: { id } });
        if (!existing) {
            throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
        }
        if (!existing.isUserGenerated) {
            throw new error_handler_1.AppError(400, 'Only user-generated campaigns can be moderated', 'INVALID_STATE');
        }
        const updateData = {
            reviewedByUserId: reviewer.id,
            reviewedAt: new Date(),
            moderationNotes: input.notes ?? null,
        };
        switch (input.action) {
            case 'approve':
                updateData.moderationStatus = client_1.CampaignModerationStatus.APPROVED;
                updateData.status = 'ACTIVE';
                updateData.rejectionReason = null;
                break;
            case 'reject':
                updateData.moderationStatus = client_1.CampaignModerationStatus.REJECTED;
                updateData.rejectionReason = input.reason ?? null;
                break;
            case 'request_changes':
                updateData.moderationStatus = client_1.CampaignModerationStatus.CHANGES_REQUESTED;
                updateData.rejectionReason = input.reason ?? null;
                break;
        }
        return database_1.prisma.campaign.update({
            where: { id },
            data: updateData,
            select: campaignSelect,
        });
    },
};
//# sourceMappingURL=campaigns.service.js.map