import { config } from "@shared/lib/config";
import { economyService } from "./economy.service";
import { lootdrops } from "@db/schema";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { eq, and, isNull, lt } from "drizzle-orm";

// Module-level state (exported for testing)
export const channelActivity: Map<string, number[]> = new Map();
export const channelCooldowns: Map<string, number> = new Map();

// Private helper function
function cleanupActivity() {
    const now = Date.now();
    const window = config.lootdrop.activityWindowMs;

    for (const [channelId, timestamps] of channelActivity.entries()) {
        const validTimestamps = timestamps.filter(t => now - t < window);
        if (validTimestamps.length === 0) {
            channelActivity.delete(channelId);
        } else {
            channelActivity.set(channelId, validTimestamps);
        }
    }
}

// Initialize cleanup interval on module load
setInterval(() => {
    cleanupActivity();
    cleanupExpiredLootdrops(true);
}, 60000);

async function cleanupExpiredLootdrops(includeClaimed: boolean = false): Promise<number> {
    try {
        const now = new Date();
        const whereClause = includeClaimed
            ? lt(lootdrops.expiresAt, now)
            : and(isNull(lootdrops.claimedBy), lt(lootdrops.expiresAt, now));

        const result = await DrizzleClient.delete(lootdrops)
            .where(whereClause)
            .returning();

        if (result.length > 0) {
            console.log(`[LootdropService] Cleaned up ${result.length} expired lootdrops.`);
        }
        return result.length;
    } catch (error) {
        console.error("Failed to cleanup lootdrops:", error);
        return 0;
    }
}

/**
 * Track channel activity and determine if a lootdrop should spawn.
 * Returns shouldSpawn: true if conditions are met (activity threshold + random chance).
 */
function trackActivity(channelId: string): { shouldSpawn: boolean } {
    const now = Date.now();

    // Check cooldown
    const cooldown = channelCooldowns.get(channelId);
    if (cooldown && now < cooldown) return { shouldSpawn: false };

    // Track activity
    const timestamps = channelActivity.get(channelId) || [];
    timestamps.push(now);
    channelActivity.set(channelId, timestamps);

    // Filter for window
    const window = config.lootdrop.activityWindowMs;
    const recentActivity = timestamps.filter(t => now - t < window);

    if (recentActivity.length >= config.lootdrop.minMessages) {
        // Chance to spawn
        if (Math.random() < config.lootdrop.spawnChance) {
            // Set cooldown
            channelCooldowns.set(channelId, now + config.lootdrop.cooldownMs);
            channelActivity.set(channelId, []);
            return { shouldSpawn: true };
        }
    }

    return { shouldSpawn: false };
}

/**
 * Calculate lootdrop reward amount and currency.
 */
function calculateReward(overrideReward?: number, overrideCurrency?: string): { reward: number; currency: string } {
    const min = config.lootdrop.reward.min;
    const max = config.lootdrop.reward.max;
    const reward = overrideReward ?? (Math.floor(Math.random() * (max - min + 1)) + min);
    const currency = overrideCurrency ?? config.lootdrop.reward.currency;
    return { reward, currency };
}

/**
 * Persist a spawned lootdrop to the database.
 */
async function persistLootdrop(messageId: string, channelId: string, reward: number, currency: string): Promise<void> {
    await DrizzleClient.insert(lootdrops).values({
        messageId,
        channelId,
        rewardAmount: reward,
        currency: currency,
        createdAt: new Date(),
        // Expire after 10 mins
        expiresAt: new Date(Date.now() + 600000)
    });
}

/**
 * Remove a lootdrop from the database. Returns the channelId for Discord cleanup.
 */
async function removeLootdrop(messageId: string): Promise<{ channelId: string } | null> {
    try {
        // First fetch it to get channel info
        const drop = await DrizzleClient.query.lootdrops.findFirst({
            where: eq(lootdrops.messageId, messageId)
        });

        if (!drop) return null;

        // Delete from DB
        await DrizzleClient.delete(lootdrops).where(eq(lootdrops.messageId, messageId));

        return { channelId: drop.channelId };
    } catch (error) {
        console.error("Error removing lootdrop:", error);
        return null;
    }
}

async function tryClaim(messageId: string, userId: string, username: string): Promise<{ success: boolean; amount?: number; currency?: string; error?: string }> {
    try {
        // Atomic update: Try to set claimedBy where it is currently null
        // This acts as a lock and check in one query
        const result = await DrizzleClient.update(lootdrops)
            .set({ claimedBy: BigInt(userId) })
            .where(and(
                eq(lootdrops.messageId, messageId),
                isNull(lootdrops.claimedBy)
            ))
            .returning();

        if (result.length === 0 || !result[0]) {
            // If update affected 0 rows, check if it was because it doesn't exist or is already claimed
            const check = await DrizzleClient.select().from(lootdrops).where(eq(lootdrops.messageId, messageId));
            if (check.length === 0) {
                return { success: false, error: "This lootdrop has expired." };
            }
            return { success: false, error: "This lootdrop has already been claimed." };
        }

        const drop = result[0];

        await economyService.modifyUserBalance(
            userId,
            BigInt(drop.rewardAmount),
            "LOOTDROP_CLAIM",
            `Claimed lootdrop in channel ${drop.channelId}`
        );

        return { success: true, amount: drop.rewardAmount, currency: drop.currency };

    } catch (error) {
        console.error("Error claiming lootdrop:", error);
        return { success: false, error: "An error occurred while processing the reward." };
    }
}

function getLootdropState() {
    let hottestChannel: { id: string; messages: number; progress: number; cooldown: boolean; } | null = null;
    let maxMessages = -1;

    const window = config.lootdrop.activityWindowMs;
    const now = Date.now();
    const required = config.lootdrop.minMessages;

    for (const [channelId, timestamps] of channelActivity.entries()) {
        // Filter valid just to be sure we are reporting accurate numbers
        const validCount = timestamps.filter(t => now - t < window).length;

        // Check cooldown
        const cooldownUntil = channelCooldowns.get(channelId);
        const isOnCooldown = !!(cooldownUntil && now < cooldownUntil);

        if (validCount > maxMessages) {
            maxMessages = validCount;
            hottestChannel = {
                id: channelId,
                messages: validCount,
                progress: Math.min(100, (validCount / required) * 100),
                cooldown: isOnCooldown
            };
        }
    }

    return {
        monitoredChannels: channelActivity.size,
        hottestChannel,
        config: {
            requiredMessages: required,
            dropChance: config.lootdrop.spawnChance
        }
    };
}

async function clearCaches() {
    channelActivity.clear();
    channelCooldowns.clear();
    console.log("[LootdropService] Caches cleared via administrative action.");
}

export const lootdropService = {
    /** Delete expired lootdrops from the database; optionally includes already-claimed ones. */
    cleanupExpiredLootdrops,
    /** Record a message in a channel and return whether a lootdrop should spawn based on activity and RNG. */
    trackActivity,
    /** Calculate a random lootdrop reward amount and currency, with optional overrides. */
    calculateReward,
    /** Save a spawned lootdrop to the database with a 10-minute expiration. */
    persistLootdrop,
    /** Remove a lootdrop by message ID and return its channel ID for Discord cleanup. */
    removeLootdrop,
    /** Atomically claim a lootdrop for a user; credits reward to their balance. */
    tryClaim,
    /** Get current lootdrop system state including the most active channel and spawn config. */
    getLootdropState,
    /** Clear all in-memory activity tracking and cooldown caches. */
    clearCaches,
};
