import { Client, Guild, Member, type JSONMember, type Presence, type Uncached } from "oceanic.js";
import { environment } from "../../config/environment";
import { getSteamGameIcon } from "../api/status";

class DiscordBot {
    private client: Client;
    private watchIds: string[] = environment.discord.watchIds.map((id) => id.toString());
    private userPresences: Map<string, Presence>;
    private connectedSockets: Set<WebSocket>;
    private usersInfo: Map<string, any>;

    constructor() {
        this.client = new Client({
            auth: `Bot ${environment.discord.auth.token}`,
            gateway: {
                intents: ["GUILDS", "GUILD_PRESENCES"]
            }
        });

        this.userPresences = new Map();
        this.usersInfo = new Map();
        this.connectedSockets = new Set();
        this.watchIds = environment.discord.watchIds.map((id) => id.toString());
    }

    public async start(): Promise<void> {
        try {
            await this.client.connect();
            await this.setupListeners();
        } catch (error) {
            console.error("Failed to connect to Discord: ", error);
        }
    }

    public async stop(): Promise<void> {
        try {
            this.client.disconnect();
        } catch (error) {
            console.error("Failed to disconnect from Discord: ", error);
        }
    }

    public getClient(): Client {
        return this.client;
    }

    public getUserPresences(): Map<string, Presence> {
        return this.userPresences;
    }

    public getUsersInfo(): Map<string, any> {
        return this.usersInfo;
    }

    public addWebSocket(socket: WebSocket): void {
        this.connectedSockets.add(socket);
    }

    public removeWebSocket(socket: WebSocket): void {
        this.connectedSockets.delete(socket);
    }

    private async broadcastPresenceUpdate(userId: string, presence: Presence): Promise<void> {
        const updatedActivities = presence.activities
            ? await Promise.all(
                presence.activities.map(async (activity) => {
                    if (activity && activity.type === 0 && (!activity.assets || !activity.assets.largeImage)) {
                        const gameName = activity.name;
                        // should use what discord uses but i dont care enough to switch it out
                        const iconUrl = await getSteamGameIcon(gameName);

                        if (iconUrl) {
                            return {
                                ...activity,
                                assets: {
                                    largeImage: iconUrl,
                                    largeText: gameName
                                }
                            };
                        }
                    }
                    return activity;
                })
            )
            : [];

        const updateMessage = JSON.stringify({
            type: "presenceUpdate",
            userId,
            presence: {
                status: presence.status,
                activities: updatedActivities
            }
        });

        this.broadcastToWebSockets(updateMessage);
    }

    private async fetchUsersInfo(): Promise<void> {
        const guild = this.client.guilds.get(environment.discord.auth.guildId);

        if (!guild) return;

        for (const id of this.watchIds) {
            try {
                const member = await guild.getMember(id);
                if (member) {
                    const memberData = this.serializeMember(member);
                    this.usersInfo.set(member.user.id, memberData);

                    const updateMessage = JSON.stringify({
                        type: "memberUpdate",
                        user: memberData
                    });

                    this.broadcastToWebSockets(updateMessage);
                }
            } catch (error) {
                console.error(`Failed to fetch member with ID ${id}:`, error);
            }
        }
    }

    private async fetchUserPresences(): Promise<void> {
        const guild = this.client.guilds.get(environment.discord.auth.guildId);

        if (!guild) return;

        this.watchIds.forEach((id) => {
            const member = guild.members.get(id);
            if (member && member.presence) {
                this.userPresences.set(member.user.id, member.presence);
            }
        });
    }

    private async setupListeners(): Promise<void> {
        this.client.on("ready", async () => {
            const user = this.client.user;
            const guild = this.client.guilds.get(environment.discord.auth.guildId);

            if (user && guild) {
                await this.fetchUserPresences();
                await this.fetchUsersInfo();
            }
        });

        this.client.on("presenceUpdate", (guild: Guild | Uncached, member: Member | Uncached, presence: Presence, oldPresence: Presence | null) => {
            if (!(member instanceof Member)) return;

            const userId = member.user.id.toString();
            if (!this.watchIds.includes(userId)) return;

            const oldStatus = oldPresence;
            const newStatus = presence;

            if (oldStatus !== newStatus) {
                this.userPresences.set(userId, presence);
                this.broadcastPresenceUpdate(userId, presence);
            }
        });

        this.client.on("guildMemberUpdate", (member: Member, oldMember: JSONMember | null) => {
            const userId = member.user.id.toString();

            if (!this.watchIds.includes(userId)) return;

            const memberData = this.serializeMember(member);
            this.usersInfo.set(userId, memberData);

            const updateMessage = JSON.stringify({
                type: "memberUpdate",
                user: memberData
            });

            this.broadcastToWebSockets(updateMessage);
        });
    }

    private serializeMember(member: Member): object {
        const user = member.user;
        return {
            id: user.id,
            username: user.username,
            discriminator: user.discriminator,
            nick: member.nick ?? user.username,
            avatar: user.avatar
                ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}`
                : `https://cdn.discordapp.com/embed/avatars/${parseInt(user.discriminator) % 5}`,
            avatarDecoration: user.avatarDecorationData?.asset
                ? `https://cdn.discordapp.com/avatar-decoration-presets/${user.avatarDecorationData.asset}`
                : null,
            roles: member.roles,
            joinedAt: member.joinedAt,
            premiumSince: member.premiumSince,
            user: user
        };
    }

    private broadcastToWebSockets(message: string): void {
        for (const socket of this.connectedSockets) {
            if (socket.readyState === WebSocket.OPEN) {
                socket.send(message);
            }
        }
    }
}

const discordBot = new DiscordBot();

export { discordBot };
export default discordBot;