import { Events, ActivityType, ChannelType } from "discord.js"
import type { Client } from "discord.js"
import { useMainPlayer } from "discord-player"
import { SpotifyExtractor } from "@discord-player/extractor"
import { YoutubeiExtractor } from "discord-player-youtubei"
import { connect } from "mongoose"
import type { Document } from "mongoose"
import { playerDisco, playerReplay } from "@/utils/player"
import { twitchClient, listener, onlineSub, offlineSub, startStreamWatching } from "@/utils/twitch"
import { logConsole, logConsoleError } from "@/utils/console"
import type { GuildPlayer, Disco, GuildTwitch, GuildFbx } from "@/types/schemas"
import * as Freebox from "@/utils/freebox"
import dbGuildInit from "@/utils/dbGuildInit"
import dbGuild from "@/schemas/guild"

export const name = Events.ClientReady
export const once = true
export async function execute(client: Client) {
	logConsole('discordjs', 'ready', { tag: client.user?.tag ?? "unknown" })
	client.user?.setActivity("some bangers...", { type: ActivityType.Listening })

	const player = useMainPlayer()
	await player.extractors.register(SpotifyExtractor, {}).then(() => { logConsole('discord_player', 'extractor_loaded', { extractor: 'Spotify' }) }).catch(console.error)
	await player.extractors.register(YoutubeiExtractor, {}).then(() => { logConsole('discord_player', 'extractor_loaded', { extractor: 'Youtube' }) }).catch(console.error)
	if (process.env.NODE_ENV === "development") console.log(player.scanDeps())

	const mongo_url = `mongodb://${process.env.MONGOOSE_USER}:${process.env.MONGOOSE_PASSWORD}@${process.env.MONGOOSE_HOST}/${process.env.MONGOOSE_DATABASE}`
	await connect(mongo_url).catch(console.error)

	const streamerIds: string[] = []

	await Promise.all(client.guilds.cache.map(async guild => {
		let guildProfile = await dbGuild.findOne({ guildId: guild.id })
		guildProfile ??= await dbGuildInit(guild)

		const dbDataPlayer = guildProfile.get("guildPlayer") as GuildPlayer
		const botInstance = dbDataPlayer.instances?.find(instance => instance.botId === client.user?.id)
		if (botInstance?.replay.trackUrl) await playerReplay(client, dbDataPlayer)

		client.disco = { interval: {} as NodeJS.Timeout }
		// eslint-disable-next-line @typescript-eslint/no-misused-promises
		client.disco.interval = setInterval(async () => {
			const guildProfile = await dbGuild.findOne({ guildId: guild.id })
			const dbDataDisco = guildProfile?.get("guildPlayer.disco") as Disco

			if (dbDataDisco.enabled) {
				const state = await playerDisco(client, guild, dbDataDisco)
				if (state === "clear") clearInterval(client.disco.interval)
			}
		}, 3000)

		// Gestion du timer LCD Freebox
		const dbDataFbx = guildProfile.get("guildFbx") as GuildFbx
		if (dbDataFbx.enabled && dbDataFbx.lcd) {
			if (dbDataFbx.lcd.enabled && dbDataFbx.lcd.botId === client.user?.id) {
				logConsole('freebox', 'lcd_timer_restored', { guild: guild.name })
				Freebox.Timer.schedule(client, guild.id, dbDataFbx)
			}
		}

		const dbDataTwitch = guildProfile.get("guildTwitch") as GuildTwitch
		if (!dbDataTwitch.enabled) return
		if (!dbDataTwitch.streamers.length) { logConsole('twitch', 'ready.no_streamers_configured', { guild: guild.name }); return }

		await Promise.all(dbDataTwitch.streamers.map(async streamer => {
			if (streamerIds.includes(streamer.twitchUserId)) return
			streamerIds.push(streamer.twitchUserId)

			const user = await twitchClient.users.getUserById(streamer.twitchUserId)
			if (!user) { logConsole('twitch', 'ready.user_not_found', { guild: guild.name, userId: streamer.twitchUserId }); return }

			const userSubs = await twitchClient.eventSub.getSubscriptionsForUser(streamer.twitchUserId)
			if (process.env.NODE_ENV === "development") {
				console.log(userSubs)
				userSubs.data.forEach(sub => { console.log(sub) })
			}
			if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.online")) {
				// eslint-disable-next-line @typescript-eslint/no-misused-promises
				listener.onStreamOnline(streamer.twitchUserId, onlineSub)
				logConsole('twitch', 'listener_registered', { type: 'stream.online', name: user.name, id: streamer.twitchUserId })
			}
			if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.offline")) {
				// eslint-disable-next-line @typescript-eslint/no-misused-promises
				listener.onStreamOffline(streamer.twitchUserId, offlineSub)
				logConsole('twitch', 'listener_registered', { type: 'stream.offline', name: user.name, id: streamer.twitchUserId })
			}

			logConsole('twitch', 'user_operational', { name: user.name, id: streamer.twitchUserId })

			const stream = await user.getStream()
			if (stream && streamer.messageId) {
				logConsole('twitch', 'ready.stream_restoration', { guild: guild.name, userName: user.name, userId: streamer.twitchUserId })

				// Vérifier que le message existe encore
				if (!dbDataTwitch.channelId) return
				const channel = await guild.channels.fetch(dbDataTwitch.channelId)
				if (channel && (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement)) {
					try {
						await channel.messages.fetch(streamer.messageId)
						startStreamWatching(guild.id, streamer.twitchUserId, user.name, streamer.messageId)
						logConsole('twitch', 'ready.monitoring_restored', { guild: guild.name, userName: user.name })
					} catch (error) {
						logConsoleError('twitch', 'ready.message_not_found', { guild: guild.name, userName: user.name }, error as Error)
						await cleanupMessageId(guildProfile, streamer.twitchUserId)
					}
				}
			} else if (streamer.messageId) {
				// Il y a un messageId mais le stream n'est plus en ligne, nettoyer
				logConsole('twitch', 'ready.stream_offline_cleanup', { guild: guild.name, userName: user.name })
				await cleanupMessageId(guildProfile, streamer.twitchUserId)
			}
		}))
	}))

	const subs = await twitchClient.eventSub.getSubscriptions()
	await Promise.all(subs.data.map(async sub => {
		if (streamerIds.includes(sub.condition.broadcaster_user_id as string)) return
		if (sub.type !== "stream.online" && sub.type !== "stream.offline") return

		await sub.unsubscribe().catch(console.error)
		logConsole('twitch', 'unsubscribed', { type: sub.type, id: sub.condition.broadcaster_user_id as string })
	}))
}

async function cleanupMessageId(guildProfile: Document, twitchUserId: string) {
	try {
		const dbData = guildProfile.get("guildTwitch") as GuildTwitch

		const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === twitchUserId)
		if (streamerIndex === -1) return

		dbData.streamers[streamerIndex].messageId = ""
				
		guildProfile.set("guildTwitch", dbData)
		guildProfile.markModified("guildTwitch")
		await guildProfile.save().catch(console.error)
	} catch (error) {
		logConsoleError('twitch', 'ready.cleanup_error', { userId: twitchUserId }, error as Error)
	}
}
