import { DrizzleClient } from "@shared/db/DrizzleClient";
import { users, transactions, moderationCases, inventory, lootdrops, items, type User } from "@db/schema";
import { desc, sql, gte, eq } from "drizzle-orm";
import { systemEvents, EVENTS } from "@shared/lib/events";
import type { RecentEvent, ActivityData } from "./dashboard.types";
import { TransactionType } from "@shared/lib/constants";

export const dashboardService = {
    /**
     * Get count of active users from database
     */
    getActiveUserCount: async (): Promise<number> => {
        const result = await DrizzleClient
            .select({ count: sql<string>`COUNT(*)` })
            .from(users)
            .where(sql`${users.isActive} = true`);

        return Number(result[0]?.count || 0);
    },

    /**
     * Get total user count
     */
    getTotalUserCount: async (): Promise<number> => {
        const result = await DrizzleClient
            .select({ count: sql<string>`COUNT(*)` })
            .from(users);

        return Number(result[0]?.count || 0);
    },

    /**
     * Get economy statistics
     */
    getEconomyStats: async (): Promise<{
        totalWealth: bigint;
        avgLevel: number;
        topStreak: number;
    }> => {
        const allUsers = await DrizzleClient.select().from(users);

        const totalWealth = allUsers.reduce(
            (acc: bigint, u: User) => acc + (u.balance || 0n),
            0n
        );

        const avgLevel = allUsers.length > 0
            ? Math.round(
                allUsers.reduce((acc: number, u: User) => acc + (u.level || 1), 0) / allUsers.length
            )
            : 1;

        const topStreak = allUsers.reduce(
            (max: number, u: User) => Math.max(max, u.dailyStreak || 0),
            0
        );

        return { totalWealth, avgLevel, topStreak };
    },

    /**
     * Get total items in circulation
     */
    getTotalItems: async (): Promise<number> => {
        const result = await DrizzleClient
            .select({ total: sql<string>`COALESCE(SUM(${inventory.quantity}), 0)` })
            .from(inventory);

        return Number(result[0]?.total || 0);
    },

    /**
     * Get recent transactions as events (last 24 hours)
     */
    getRecentTransactions: async (limit: number = 10): Promise<RecentEvent[]> => {
        const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);

        const recentTx = await DrizzleClient.query.transactions.findMany({
            limit,
            orderBy: [desc(transactions.createdAt)],
            where: gte(transactions.createdAt, oneDayAgo),
            with: {
                user: true,
            },
        });

        return recentTx.map((tx) => ({
            type: 'info' as const,
            message: `${tx.user?.username || 'Unknown'}: ${tx.description || 'Transaction'}`,
            timestamp: tx.createdAt || new Date(),
            icon: getTransactionIcon(tx.type),
        }));
    },

    /**
     * Get recent moderation cases as events (last 24 hours)
     */
    getRecentModerationCases: async (limit: number = 10): Promise<RecentEvent[]> => {
        const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);

        const recentCases = await DrizzleClient.query.moderationCases.findMany({
            limit,
            orderBy: [desc(moderationCases.createdAt)],
            where: gte(moderationCases.createdAt, oneDayAgo),
        });

        return recentCases.map((modCase) => ({
            type: modCase.type === 'warn' || modCase.type === 'ban' ? 'error' : 'info',
            message: `${modCase.type.toUpperCase()}: ${modCase.username} - ${modCase.reason}`,
            timestamp: modCase.createdAt || new Date(),
            icon: getModerationIcon(modCase.type as string),
        }));
    },

    /**
     * Get combined recent events (transactions + moderation)
     */
    getRecentEvents: async (limit: number = 10): Promise<RecentEvent[]> => {
        const [txEvents, modEvents] = await Promise.all([
            dashboardService.getRecentTransactions(limit),
            dashboardService.getRecentModerationCases(limit),
        ]);

        // Combine and sort by timestamp
        const allEvents = [...txEvents, ...modEvents]
            .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
            .slice(0, limit);

        return allEvents;
    },

    /**
     * Records a new internal event and broadcasts it via WebSocket
     */
    recordEvent: async (event: Omit<RecentEvent, 'timestamp'>): Promise<void> => {
        const fullEvent: RecentEvent = {
            ...event,
            timestamp: new Date(),
        };

        // Broadcast to WebSocket clients
        try {
            systemEvents.emit(EVENTS.DASHBOARD.NEW_EVENT, {
                ...fullEvent,
                timestamp: (fullEvent.timestamp instanceof Date)
                    ? fullEvent.timestamp.toISOString()
                    : fullEvent.timestamp
            });
        } catch (e) {
            console.error("Failed to emit system event:", e);
        }
    },

    /**
     * Get hourly activity aggregation for the last 24 hours
     */
    getActivityAggregation: async (): Promise<ActivityData[]> => {
        const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);

        // Postgres aggregation
        // We treat everything as a transaction.
        // We treat everything except TRANSFER_IN as a 'command' (to avoid double counting transfers)
        const result = await DrizzleClient
            .select({
                hour: sql<string>`date_trunc('hour', ${transactions.createdAt})`,
                transactions: sql<string>`COUNT(*)`,
                commands: sql<string>`COUNT(*) FILTER (WHERE ${transactions.type} != ${TransactionType.TRANSFER_IN})`
            })
            .from(transactions)
            .where(gte(transactions.createdAt, twentyFourHoursAgo))
            .groupBy(sql`1`)
            .orderBy(sql`1`);

        // Map into a record for easy lookups
        const dataMap = new Map<string, { commands: number, transactions: number }>();
        result.forEach(row => {
            if (!row.hour) return;
            const dateStr = new Date(row.hour).toISOString();
            dataMap.set(dateStr, {
                commands: Number(row.commands),
                transactions: Number(row.transactions)
            });
        });

        // Generate the last 24 hours of data
        const activity: ActivityData[] = [];
        const current = new Date();
        current.setHours(current.getHours(), 0, 0, 0);

        for (let i = 23; i >= 0; i--) {
            const h = new Date(current.getTime() - i * 60 * 60 * 1000);
            const iso = h.toISOString();
            const existing = dataMap.get(iso);

            activity.push({
                hour: iso,
                commands: existing?.commands || 0,
                transactions: existing?.transactions || 0
            });
        }

        return activity;
    },
    /**
     * Get active lootdrops
     */
    getActiveLootdrops: async () => {
        const activeDrops = await DrizzleClient.query.lootdrops.findMany({
            where: (lootdrops, { isNull }) => isNull(lootdrops.claimedBy),
            limit: 1,
            orderBy: desc(lootdrops.createdAt)
        });

        return activeDrops;
    },

    /**
     * Get leaderboards (Top 3 Levels and Wealth)
     */
    getLeaderboards: async () => {
        const topLevels = await DrizzleClient.select({
            username: users.username,
            level: users.level,
        })
            .from(users)
            .orderBy(desc(users.level))
            .limit(10);

        const topWealth = await DrizzleClient.select({
            username: users.username,
            balance: users.balance,
        })
            .from(users)
            .orderBy(desc(users.balance))
            .limit(10);



        const topNetWorth = await DrizzleClient.select({
            username: users.username,
            netWorth: sql<bigint>`${users.balance} + COALESCE(SUM(${items.price} * ${inventory.quantity}), 0)`.as('net_worth')
        })
            .from(users)
            .leftJoin(inventory, eq(users.id, inventory.userId))
            .leftJoin(items, eq(inventory.itemId, items.id))
            .groupBy(users.id, users.username, users.balance)
            .orderBy(desc(sql`net_worth`))
            .limit(10);

        return {
            topLevels,
            topWealth: topWealth.map(u => ({ ...u, balance: (u.balance || 0n).toString() })),
            topNetWorth: topNetWorth.map(u => ({ ...u, netWorth: (u.netWorth || 0n).toString() }))
        };
    }
};

/**
 * Helper to get icon for transaction type
 */
function getTransactionIcon(type: string): string {
    if (type.includes("LOOT")) return "🌠";
    if (type.includes("GIFT")) return "🎁";
    if (type.includes("SHOP")) return "🛒";
    if (type.includes("DAILY")) return "☀️";
    if (type.includes("QUEST")) return "📜";
    if (type.includes("TRANSFER")) return "💸";
    return "💫";
}

/**
 * Helper to get icon for moderation type
 */
function getModerationIcon(type: string): string {
    switch (type) {
        case 'warn': return '⚠️';
        case 'timeout': return '⏸️';
        case 'kick': return '👢';
        case 'ban': return '🔨';
        case 'note': return '📝';
        case 'prune': return '🧹';
        default: return '🛡️';
    }
}
