import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, MessageFlags, ComponentType } from "discord.js"
import type { ButtonInteraction, Client, Guild, GuildChannel, Locale } from "discord.js"
import { useMainPlayer, useQueue } from "discord-player"
import type { GuildPlayer, Disco } from "@/types/schemas"
import type { PlayerMetadata } from "@/types/player"
import uptime from "./uptime"
import dbGuild from "@/schemas/guild"
import { t, getGuildLocale } from "./i18n"
import { logConsole, logConsoleError } from "./console"

const progressIntervals = new Map<string, NodeJS.Timeout>()

export function startProgressSaving(guildId: string, botId: string) {
	if (!guildId || !botId) { logConsole('discord_player', 'progress_saving.missing_ids'); return }
	
	logConsole('discord_player', 'progress_saving.start', { guildId, botId })

	const key = `${guildId}-${botId}`
	if (progressIntervals.has(key)) {
		clearInterval(progressIntervals.get(key))
		progressIntervals.delete(key)
	}

	// eslint-disable-next-line @typescript-eslint/no-misused-promises
	const interval = setInterval(async () => {
		try {
			const queue = useQueue(guildId)
			if (!queue || !queue.isPlaying() || !queue.currentTrack) { await stopProgressSaving(guildId, botId); return }

			const guildProfile = await dbGuild.findOne({ guildId })
			if (!guildProfile) { await stopProgressSaving(guildId, botId); return }

			const dbData = guildProfile.get("guildPlayer") as GuildPlayer
			dbData.instances ??= []

			const instanceIndex = dbData.instances.findIndex(instance => instance.botId === botId)
			if (instanceIndex === -1) {
				dbData.instances.push({ botId, replay: {
					textChannelId: (queue.metadata as PlayerMetadata).channel?.id ?? "",
					voiceChannelId: queue.connection?.joinConfig.channelId ?? "",
					trackUrl: queue.currentTrack.url,
					progress: queue.node.playbackTime
				} })
			} else {
				dbData.instances[instanceIndex].replay.trackUrl = queue.currentTrack.url
				dbData.instances[instanceIndex].replay.progress = queue.node.playbackTime
			}

			guildProfile.set("guildPlayer", dbData)
			guildProfile.markModified("guildPlayer")
			await guildProfile.save().catch(console.error)
		} catch (error) {
			logConsoleError('discord_player', 'progress_saving.error', { guildId, botId }, error as Error)
			await stopProgressSaving(guildId, botId)
		}
	}, 3000)

	progressIntervals.set(key, interval)
}

export async function stopProgressSaving(guildId: string, botId: string) {
	if (!guildId || !botId) { logConsole('discord_player', 'progress_saving.missing_ids'); return }

	logConsole('discord_player', 'progress_saving.stop', { guildId, botId })

	const key = `${guildId}-${botId}`
	if (progressIntervals.has(key)) {
		clearInterval(progressIntervals.get(key))
		progressIntervals.delete(key)
	}

	const guildProfile = await dbGuild.findOne({ guildId: guildId })
	if (!guildProfile) { logConsole('discord_player', 'progress_saving.database_not_exist'); return }

	const dbData = guildProfile.get("guildPlayer") as GuildPlayer
	if (dbData.instances) {
		const instanceIndex = dbData.instances.findIndex(instance => instance.botId === botId)
		if (instanceIndex === -1) return

		dbData.instances[instanceIndex].replay = {
			textChannelId: "",
			voiceChannelId: "",
			trackUrl: "",
			progress: 0
		}
			
		guildProfile.set("guildPlayer", dbData)
		guildProfile.markModified("guildPlayer")
		await guildProfile.save().catch(console.error)
	}
}

export async function playerReplay(client: Client, dbData: GuildPlayer) {
	const botId = client.user?.id ?? ""
	const instance = dbData.instances?.find(instance => instance.botId === botId)
	if (!instance) { logConsole('discordjs', 'replay.no_data', { botId }); return }
	
	if (!instance.replay.textChannelId) { logConsole('discordjs', 'replay.no_text_channel_id', { botId }); return }
	if (!instance.replay.voiceChannelId) { logConsole('discordjs', 'replay.no_voice_channel_id', { botId }); return }
	
	const textChannel = await client.channels.fetch(instance.replay.textChannelId)
	if (!textChannel || (textChannel.type !== ChannelType.GuildText && textChannel.type !== ChannelType.GuildAnnouncement)) {
		logConsole('discordjs', 'replay.text_channel_not_found', { channelId: instance.replay.textChannelId, botId })
		return
	}

	const voiceChannel = await client.channels.fetch(instance.replay.voiceChannelId)
	if (!voiceChannel || (voiceChannel.type !== ChannelType.GuildVoice && voiceChannel.type !== ChannelType.GuildStageVoice)) {
		logConsole('discordjs', 'replay.voice_channel_not_found', { channelId: instance.replay.textChannelId, botId })
		return
	}

	const player = useMainPlayer()
	const queue = player.nodes.create((textChannel as GuildChannel).guild, {
		metadata: {
			channel: textChannel,
			client: textChannel.guild.members.me,
			requestedBy: client.user
		},
		selfDeaf: true,
		volume: 20,
		leaveOnEmpty: true,
		leaveOnEmptyCooldown: 30000,
		leaveOnEnd: true,
		leaveOnEndCooldown: 300000
	})

	try { if (!queue.connection) await queue.connect(voiceChannel) }
	catch (error) { logConsoleError('discord_player', 'replay.connect_error', {}, error as Error) }

	if (!instance.replay.trackUrl) return
	
	const guildLocale = await getGuildLocale(queue.guild.id)
	const result = await player.search(instance.replay.trackUrl, { requestedBy: client.user ?? undefined })
	if (!result.hasTracks()) await textChannel.send(t(guildLocale, "player.no_track_found", { url: instance.replay.trackUrl }))
	const track = result.tracks[0]

	const entry = queue.tasksQueue.acquire()
	await entry.getTask()
	queue.addTrack(track)

	try {
		if (!queue.isPlaying()) await queue.node.play()
		if (instance.replay.progress) await queue.node.seek(instance.replay.progress)
		startProgressSaving(queue.guild.id, botId)
		await textChannel.send(t(guildLocale, "player.music_restarted"))
	}
	catch (error) { logConsoleError('discord_player', 'replay.play_error', {}, error as Error) }
	finally { queue.tasksQueue.release() }
}

export async function playerDisco(client: Client, guild: Guild, dbData: Disco) {
	try {
		if (!dbData.channelId) {
			logConsole('discord_player', 'disco.channel_not_configured', { guild: guild.name })
			clearInterval(client.disco.interval)
			return "clear"
		}

		const channel = await client.channels.fetch(dbData.channelId)
		if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
			logConsole('discord_player', 'disco.channel_not_found', { guild: guild.name, channelId: dbData.channelId })
			clearInterval(client.disco.interval)
			return "clear"
		}

		const guildLocale = await getGuildLocale(guild.id)
		const { embed, components } = generatePlayerEmbed(guild, guildLocale)
		if (components && embed.data.footer) embed.setFooter({ text: `Uptime: ${uptime(client.uptime)} \n ${embed.data.footer.text}` })
		else embed.setFooter({ text: `Uptime: ${uptime(client.uptime)}` })

		const messages = await channel.messages.fetch()
		const bots = ["1065047326860783636", "1119343522059927684", "1119344050412204032", "1210714000321548329", "660961595006124052"]
		await Promise.all(messages.map(async msg => { if (!bots.includes(msg.author.id)) await msg.delete() }))

		const botMessage = messages.find(msg => client.user && msg.author.id === client.user.id)
		if (botMessage) {
			if (!components && botMessage.components.length > 0) {
				await botMessage.delete()
				return await channel.send({ embeds: [embed] })
			}
			else if (components) return await botMessage.edit({ embeds: [embed], components })
			else return await botMessage.edit({ embeds: [embed] })
		}
		else return await channel.send({ embeds: [embed] })
	} catch (error) {
		logConsoleError('discord_player', 'disco.general_error', {}, error as Error)
		return "clear"
	}
}

export async function playerEdit(interaction: ButtonInteraction) {
	const guild = interaction.guild
	if (!guild) return interaction.reply({ content: t(interaction.locale, "common.no_dm"), flags: MessageFlags.Ephemeral })

	const { components } = generatePlayerEmbed(guild, interaction.locale)
	if (!components) return

	components.forEach(actionRow => { actionRow.components.forEach((button) => button.setDisabled(true)) })
	await interaction.update({ components })
}

export function generatePlayerEmbed(guild: Guild, locale: Locale | string) {
	const embed = new EmbedBuilder().setColor("#ffc370")

	const queue = useQueue(guild.id)
	if (!queue) {
		embed.setTitle(t(locale, "player.no_session"))
		return { embed, components: null }
	}

	const track = queue.currentTrack
	if (!track) {
		embed.setTitle(t(locale, "player.no_track"))
		return { embed, components: null }
	}

	const sourceValue = track.source === "spotify" ? t(locale, "player.sources.spotify") : 
		track.source === "youtube" ? t(locale, "player.sources.youtube") : 
		t(locale, "player.sources.unknown")

	const loopValue = queue.repeatMode === 3 ? t(locale, "player.loop_modes.autoplay") : 
		queue.repeatMode === 2 ? t(locale, "player.loop_modes.queue") : 
		queue.repeatMode === 1 ? t(locale, "player.loop_modes.track") : 
		t(locale, "player.loop_modes.off")

	const progressionName = queue.node.isPaused() ? 
		t(locale, "player.progression_paused") : 
		t(locale, "player.progression")

	embed
		.setTitle(track.title)
		.setAuthor({ name: track.author })
		.setURL(track.url)
		.setImage(track.thumbnail)
		.addFields(
			{ name: t(locale, "player.duration"), value: track.duration, inline: true },
			{ name: t(locale, "player.source"), value: sourceValue, inline: true },
			{ name: t(locale, "player.volume"), value: `${queue.node.volume}%`, inline: true },
			{ name: progressionName, value: queue.node.createProgressBar() ?? t(locale, "common.none") },
			{ name: t(locale, "player.loop"), value: loopValue, inline: true }
		)
		.setDescription(`**${t(locale, "player.next_track")} :** ${queue.tracks.data[0] ? queue.tracks.data[0].title : t(locale, "player.no_next_track")}`)
		.setFooter({ text: t(locale, "player.requested_by", { user: track.requestedBy ? track.requestedBy.tag : t(locale, "common.unknown") }) })

	const components = [
		new ActionRowBuilder<ButtonBuilder>().addComponents(
			new ButtonBuilder().setLabel(queue.node.isPaused() ? "▶️" : "⏸️").setStyle(2).setCustomId(queue.node.isPaused() ? "player_resume" : "player_pause"),
			new ButtonBuilder().setLabel("⏹️").setStyle(2).setCustomId("player_stop"),
			new ButtonBuilder().setLabel("⏭️").setStyle(2).setCustomId("player_skip").setDisabled(queue.tracks.data.length !== 0),
			new ButtonBuilder().setLabel("🔉").setStyle(2).setCustomId("player_volume_down").setDisabled(queue.node.volume === 0),
			new ButtonBuilder().setLabel("🔊").setStyle(2).setCustomId("player_volume_up").setDisabled(queue.node.volume === 100)
		),
		new ActionRowBuilder<ButtonBuilder>().addComponents(
			new ButtonBuilder().setLabel("🔀").setStyle(2).setCustomId("player_shuffle"),
			new ButtonBuilder().setLabel("🔁").setStyle(2).setCustomId("player_loop"),
			new ButtonBuilder().setLabel("⏮️").setStyle(2).setCustomId("player_previous").setDisabled(queue.history.previousTrack ? false : true)
		)
	]
	return { embed, components }
}

/**
 * Génère l'embed et les composants pour la configuration du module Disco
 * @param dbData - Données du module Disco depuis la base de données
 * @param client - Client Discord
 * @param guildId - ID de la guilde
 * @param locale - Locale pour la traduction
 * @returns Objet contenant l'embed et les composants
 */
export function generateDiscoEmbed(dbData: Disco, client: Client, guildId: string, locale: Locale) {
	// Récupérer les informations du canal
	let channelInfo = t(locale, "player.disco.channel_not_configured")
	if (dbData.channelId) {
		const guild = client.guilds.cache.get(guildId)
		const channel = guild?.channels.cache.get(dbData.channelId)
		channelInfo = channel ? `<#${channel.id}>` : t(locale, "player.common.channel_not_found")
	}

	// Créer l'embed principal
	const embed = new EmbedBuilder()
		.setTitle(t(locale, "player.disco.title"))
		.setColor(dbData.enabled ? 0xFFC370 : 0x808080)
		.setDescription(dbData.enabled ? 
			t(locale, "player.disco.description_enabled") :
			t(locale, "player.disco.description_disabled")
		)
		.addFields(
			{ name: t(locale, "common.status"), value: dbData.enabled ? t(locale, "player.common.enabled") : t(locale, "player.common.disabled"), inline: true },
			{ name: t(locale, "common.channel"), value: channelInfo, inline: true }
		)
		.setTimestamp()

	// Bouton toggle - désactivé si pas de canal configuré pour l'activation
	const toggleButton = new ButtonBuilder()
		.setCustomId(dbData.enabled ? "player_disco_disable" : "player_disco_enable")
		.setLabel(dbData.enabled ? t(locale, "common.disable") : t(locale, "common.enable"))
		.setStyle(dbData.enabled ? ButtonStyle.Danger : ButtonStyle.Success)
		.setEmoji(dbData.enabled ? "❌" : "✅")

	// Désactiver le bouton d'activation si aucun canal n'est configuré
	if (!dbData.enabled && !dbData.channelId) {
		toggleButton.setDisabled(true)
	}

	// Bouton de configuration du canal
	const channelButton = new ButtonBuilder()
		.setCustomId("player_disco_channel")
		.setLabel(t(locale, "player.common.configure_channel"))
		.setStyle(ButtonStyle.Secondary)
		.setEmoji("📺")

	const components = [
		{
			type: ComponentType.ActionRow,
			components: [toggleButton, channelButton]
		}
	]

	return { embed, components }
}
