import { Guild } from "../models/guildModel.ts";
import type { TLeaderboardEntryDocument } from "../models/leaderboardModel.ts";
import { Leaderboard } from "../models/leaderboardModel.ts";
import type { ILeaderboardEntryClient } from "../types/leaderboardTypes.ts";
import { handleGuildGoalProgress } from "./guildService.ts";
import { getWorldState } from "./worldStateService.ts";
import { Types } from "mongoose";

export const submitLeaderboardScore = async (
    schedule: "weekly" | "daily" | "events",
    leaderboard: string,
    ownerId: string | Types.ObjectId,
    displayName: string,
    score: number,
    guildId: string | undefined
): Promise<void> => {
    let expiry: Date | undefined;
    if (schedule == "daily") {
        expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000);
    } else if (schedule == "weekly") {
        const EPOCH = 1734307200 * 1000; // Monday
        const week = Math.trunc((Date.now() - EPOCH) / 604800000);
        const weekStart = EPOCH + week * 604800000;
        const weekEnd = weekStart + 604800000;
        expiry = new Date(weekEnd);
    }

    if (guildId) {
        const guild = (await Guild.findById(guildId, "Name Tier GoalProgress VaultDecoRecipes"))!;
        if (schedule == "events") {
            const prevAccount = await Leaderboard.findOne(
                { leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
                "score"
            );
            const delta = score - (prevAccount?.score ?? 0);
            if (delta > 0) {
                await Leaderboard.findOneAndUpdate(
                    { leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
                    { $inc: { score: delta }, $set: { displayName: guild.Name, guildTier: guild.Tier } },
                    { upsert: true }
                );
                const goal = getWorldState(undefined, false, false).Goals.find(x => x.ScoreMaxTag == leaderboard);
                if (goal) {
                    await handleGuildGoalProgress(guild, {
                        Count: delta,
                        Tag: goal.Tag,
                        goalId: new Types.ObjectId(goal._id.$oid)
                    });
                }
            }
        } else {
            await Leaderboard.findOneAndUpdate(
                { leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
                { $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } },
                { upsert: true }
            );
        }
    }

    await Leaderboard.findOneAndUpdate(
        { leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
        { $max: { score }, $set: { displayName, guildId, expiry } },
        { upsert: true }
    );
};

export const getLeaderboard = async (
    leaderboard: string,
    before: number,
    after: number,
    pivotId: string | undefined,
    guildId: string | undefined,
    guildTier: number | undefined
): Promise<ILeaderboardEntryClient[]> => {
    leaderboard = leaderboard.replace("archived", guildTier || guildId ? "events.guilds" : "events.accounts");
    const filter: { leaderboard: string; guildId?: string; guildTier?: number } = { leaderboard };
    if (guildId) {
        filter.guildId = guildId;
    }
    if (guildTier) {
        filter.guildTier = guildTier;
    }
    if (!filter.leaderboard.includes(".")) {
        filter.leaderboard = `events.${guildTier || guildId ? "guilds" : "accounts"}.${filter.leaderboard}`;
    }

    let entries: TLeaderboardEntryDocument[];
    let r: number;
    if (pivotId) {
        const pivotDoc = await Leaderboard.findOne({ ...filter, ownerId: pivotId });
        if (!pivotDoc) {
            return [];
        }
        const beforeDocs = await Leaderboard.find({
            ...filter,
            score: { $gt: pivotDoc.score }
        })
            .sort({ score: 1 })
            .limit(before);
        const afterDocs = await Leaderboard.find({
            ...filter,
            score: { $lt: pivotDoc.score }
        })
            .sort({ score: -1 })
            .limit(after);
        entries = [...beforeDocs.reverse(), pivotDoc, ...afterDocs];
        r =
            (await Leaderboard.countDocuments({
                ...filter,
                score: { $gt: pivotDoc.score }
            })) - beforeDocs.length;
    } else {
        entries = await Leaderboard.find(filter)
            .sort({ score: -1 })
            .skip(before)
            .limit(after - before);
        r = before;
    }
    const res: ILeaderboardEntryClient[] = [];
    for (const entry of entries) {
        res.push({
            _id: entry.ownerId.toString(),
            s: entry.score,
            r: ++r,
            n: entry.displayName
        });
    }
    return res;
};
