import { Collection, Message, PermissionFlagsBits } from "discord.js";
import type { TextBasedChannel } from "discord.js";
import type { PruneOptions, PruneResult, PruneProgress } from "@/modules/moderation/prune.types";
import { config } from "@shared/lib/config";

export class PruneService {
    /**
     * Delete messages from a channel based on provided options
     */
    static async deleteMessages(
        channel: TextBasedChannel,
        options: PruneOptions,
        progressCallback?: (progress: PruneProgress) => Promise<void>
    ): Promise<PruneResult> {
        // Validate channel permissions
        if (!('permissionsFor' in channel)) {
            throw new Error("Cannot check permissions for this channel type");
        }

        const permissions = channel.permissionsFor(channel.client.user!);
        if (!permissions?.has(PermissionFlagsBits.ManageMessages)) {
            throw new Error("Missing permission to manage messages in this channel");
        }

        const { amount, userId, all } = options;
        const batchSize = config.moderation.prune.batchSize;
        const batchDelay = config.moderation.prune.batchDelayMs;

        let totalDeleted = 0;
        let totalSkipped = 0;
        let requestedCount = amount || 10;
        let lastMessageId: string | undefined;
        let username: string | undefined;

        if (all) {
            // Delete all messages in batches
            const estimatedTotal = await this.estimateMessageCount(channel);
            requestedCount = estimatedTotal;

            while (true) {
                const messages = await this.fetchMessages(channel, batchSize, lastMessageId);

                if (messages.size === 0) break;

                const { deleted, skipped } = await this.processBatch(
                    channel,
                    messages,
                    userId
                );

                totalDeleted += deleted;
                totalSkipped += skipped;

                // Update progress
                if (progressCallback) {
                    await progressCallback({
                        current: totalDeleted,
                        total: estimatedTotal
                    });
                }

                // If we deleted fewer than we fetched, we've hit old messages
                if (deleted < messages.size) {
                    break;
                }

                // Get the ID of the last message for pagination
                const lastMessage = Array.from(messages.values()).pop();
                lastMessageId = lastMessage?.id;

                // Delay to avoid rate limits
                if (messages.size >= batchSize) {
                    await this.delay(batchDelay);
                }
            }
        } else {
            // Delete specific amount
            const limit = Math.min(amount || 10, config.moderation.prune.maxAmount);
            const messages = await this.fetchMessages(channel, limit, undefined);

            const { deleted, skipped } = await this.processBatch(
                channel,
                messages,
                userId
            );

            totalDeleted = deleted;
            totalSkipped = skipped;
            requestedCount = limit;
        }

        // Get username if filtering by user
        if (userId && totalDeleted > 0) {
            try {
                const user = await channel.client.users.fetch(userId);
                username = user.username;
            } catch {
                username = "Unknown User";
            }
        }

        return {
            deletedCount: totalDeleted,
            requestedCount,
            filtered: !!userId,
            username,
            skippedOld: totalSkipped > 0 ? totalSkipped : undefined
        };
    }

    /**
     * Fetch messages from a channel
     */
    private static async fetchMessages(
        channel: TextBasedChannel,
        limit: number,
        before?: string
    ): Promise<Collection<string, Message>> {
        if (!('messages' in channel)) {
            return new Collection();
        }

        return await channel.messages.fetch({
            limit,
            before
        });
    }

    /**
     * Process a batch of messages for deletion
     */
    private static async processBatch(
        channel: TextBasedChannel,
        messages: Collection<string, Message>,
        userId?: string
    ): Promise<{ deleted: number; skipped: number }> {
        if (!('bulkDelete' in channel)) {
            throw new Error("This channel type does not support bulk deletion");
        }

        // Filter by user if specified
        let messagesToDelete = messages;
        if (userId) {
            messagesToDelete = messages.filter(msg => msg.author.id === userId);
        }

        if (messagesToDelete.size === 0) {
            return { deleted: 0, skipped: 0 };
        }

        try {
            // bulkDelete with filterOld=true will automatically skip messages >14 days
            const deleted = await channel.bulkDelete(messagesToDelete, true);
            const skipped = messagesToDelete.size - deleted.size;

            return {
                deleted: deleted.size,
                skipped
            };
        } catch (error) {
            console.error("Error during bulk delete:", error);
            throw new Error("Failed to delete messages");
        }
    }

    /**
     * Estimate the total number of messages in a channel
     */
    static async estimateMessageCount(channel: TextBasedChannel): Promise<number> {
        if (!('messages' in channel)) {
            return 0;
        }

        try {
            // Fetch a small sample to get the oldest message
            const sample = await channel.messages.fetch({ limit: 1 });
            if (sample.size === 0) return 0;

            // This is a rough estimate - Discord doesn't provide exact counts
            // We'll return a conservative estimate
            const oldestMessage = sample.first();
            const channelAge = Date.now() - (oldestMessage?.createdTimestamp || Date.now());
            const estimatedRate = 100; // messages per day (conservative)
            const daysOld = channelAge / (1000 * 60 * 60 * 24);

            return Math.max(100, Math.round(daysOld * estimatedRate));
        } catch {
            return 100; // Default estimate
        }
    }

    /**
     * Helper to delay execution
     */
    private static delay(ms: number): Promise<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}
