// ENVIRONMENT VARIABLES
const clientId = process.env.TWITCH_APP_ID
const clientSecret = process.env.TWITCH_APP_SECRET
if (!clientId || !clientSecret) {
	logConsole('twitch', 'missing_credentials')
	process.exit(1)
}

// PACKAGES
import { AppTokenAuthProvider } from "@twurple/auth"
import { ApiClient } from "@twurple/api"
import { ReverseProxyAdapter, EventSubHttpListener } from "@twurple/eventsub-http"
import { NgrokAdapter } from "@twurple/eventsub-ngrok"
import type { EventSubStreamOnlineEvent, EventSubStreamOfflineEvent } from "@twurple/eventsub-base"
import { EmbedBuilder, ChannelType, ComponentType, ButtonBuilder, ButtonStyle, Locale } from "discord.js"
import type { Client, Guild } from "discord.js"
import discordClient from "@/index"
import type { GuildTwitch } from "@/types/schemas"
import dbGuild from "@/schemas/guild"
import { t, getGuildLocale } from "@/utils/i18n"
import { logConsole, logConsoleError } from "@/utils/console"

// Twurple API client setup
const authProvider = new AppTokenAuthProvider(clientId, clientSecret)
export const twitchClient = new ApiClient({ authProvider })

let adapter
if (process.env.NODE_ENV === "development") {
	const authtoken = process.env.NGROK_AUTHTOKEN

	logConsole('twitch', 'starting_listener_ngrok')
	adapter = new NgrokAdapter({ ngrokConfig: { authtoken } })
} else {
	const hostName = process.env.TWURPLE_HOSTNAME ?? "localhost"
	const port = process.env.TWURPLE_PORT ?? "3000"

	logConsole('twitch', 'starting_listener_port', { port })
	adapter = new ReverseProxyAdapter({ hostName, port: parseInt(port) })
}

const secret = process.env.TWURPLE_SECRET ?? "VeryUnsecureSecretPleaseChangeMe"
export const listener = new EventSubHttpListener({ apiClient: twitchClient, adapter, secret })
listener.start()

// Twurple subscriptions callback functions
export const onlineSub = async (event: EventSubStreamOnlineEvent) => {
	logConsole('twitch', 'stream_online', { streamer: event.broadcasterName, id: event.broadcasterId })

	const results = await Promise.allSettled(discordClient.guilds.cache.map(async guild => {
		const processingKey = `${guild.id}-${event.broadcasterId}`
		try {
			if (processingStreamers.has(processingKey)) { logConsole('twitch', 'streamer_already_processing', { guildName: guild.name, broadcasterName: event.broadcasterName }); return }
			processingStreamers.add(processingKey)

			logConsole('twitch', 'processing_guild', { name: guild.name, id: guild.id, streamer: event.broadcasterName })

			const notification = await generateNotification(guild, event.broadcasterId, event.broadcasterName)
			if (notification.status !== "ok") { logConsole('twitch', 'notification_failed', { guild: guild.name, status: notification.status }); return }

			const { guildProfile, dbData, channel, content, embed } = notification
			if (!dbData) { logConsole('twitch', 'no_db_data', { guild: guild.name }); return }

			const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === event.broadcasterId)
			if (streamerIndex === -1) { logConsole('twitch', 'streamer_not_found', { guild: guild.name, streamer: event.broadcasterName }); return }

			if (dbData.streamers[streamerIndex].messageId) { logConsole('twitch', 'message_exists', { guild: guild.name, streamer: event.broadcasterName }); return }

			logConsole('twitch', 'sending_notification', { guild: guild.name, streamer: event.broadcasterName })
			const message = await channel.send({ content, embeds: [embed] })
			logConsole('twitch', 'message_sent', { guild: guild.name, id: message.id })

			dbData.streamers[streamerIndex].messageId = message.id

			guildProfile.set("guildTwitch", dbData)
			guildProfile.markModified("guildTwitch")
			await guildProfile.save().catch(console.error)

			startStreamWatching(guild.id, event.broadcasterId, event.broadcasterName, message.id)
		} catch (error) {
			processingStreamers.delete(processingKey)
			logConsoleError('twitch', 'error_processing_guild', { name: guild.name }, error as Error)
		} finally {
			processingStreamers.delete(processingKey)
		}
	}))

	results.forEach((result, index) => {
		if (result.status === "rejected") logConsoleError('twitch', 'guild_failed', { index: index.toString() }, result.reason instanceof Error ? result.reason : new Error(String(result.reason)))
	})
}

export const offlineSub = async (event: EventSubStreamOfflineEvent) => {
	logConsole('twitch', 'stream_offline', { streamer: event.broadcasterName, id: event.broadcasterId })

	await Promise.all(discordClient.guilds.cache.map(async guild => {
		await stopStreamWatching(guild.id, event.broadcasterId, event.broadcasterName)
	}))
}

// Stream upadting intervals
const streamIntervals = new Map<string, NodeJS.Timeout>()

// Tracking des streamers en cours de traitement pour éviter les doublons
const processingStreamers = new Set<string>()

export function startStreamWatching(guildId: string, streamerId: string, streamerName: string, messageId: string) {
	logConsole('twitch', 'start_watching', { streamer: streamerName, id: streamerId, guildId })

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

	// eslint-disable-next-line @typescript-eslint/no-misused-promises
	const interval = setInterval(async () => {
		try {
			const guild = await discordClient.guilds.fetch(guildId)
			const notification = await generateNotification(guild, streamerId, streamerName)
			if (notification.status !== "ok") { logConsole('twitch', 'notification_failed', { guild: guild.name, status: notification.status }); return }

			const { channel, content, embed } = notification
			if (!embed) { logConsole('twitch', 'embed_missing', { guild: guild.name }); return }

			try {
				const message = await channel.messages.fetch(messageId)
				await message.edit({ content, embeds: [embed] })
			} catch (error) {
				logConsoleError('twitch', 'error_editing_message', { guild: guild.name, streamer: streamerName, id: streamerId }, error as Error)
			}
		} catch (error) {
			logConsoleError('twitch', 'error_watching', { streamer: streamerName, id: streamerId, guildId }, error as Error)
			await stopStreamWatching(guildId, streamerId, streamerName)
		}
	}, 60000)

	streamIntervals.set(key, interval)
}

export async function stopStreamWatching(guildId: string, streamerId: string, streamerName: string) {
	logConsole('twitch', 'stop_watching', { streamer: streamerName, id: streamerId })

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

	try {
		const guild = await discordClient.guilds.fetch(guildId)
		const guildProfile = await dbGuild.findOne({ guildId: guild.id })
		if (!guildProfile) { logConsole('twitch', 'database_not_exist', { guild: guild.name }); return }

		const dbData = guildProfile.get("guildTwitch") as GuildTwitch
		if (!dbData.enabled || !dbData.channelId) { logConsole('twitch', 'database_not_exist', { guild: guild.name }); return }

		const channel = await guild.channels.fetch(dbData.channelId)
		if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
			logConsole('twitch', 'channel_not_found', { guild: guild.name, channelId: dbData.channelId })
			return
		}

		const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
		if (!streamer) { logConsole('twitch', 'streamer_not_found', { guild: guild.name, streamer: streamerName, id: streamerId }); return }

		const messageId = streamer.messageId
		if (!messageId) { logConsole('twitch', 'message_id_not_found', { guild: guild.name, streamer: streamerName, id: streamerId }); return }

		const user = await twitchClient.users.getUserById(streamerId)
		if (!user) logConsole('twitch', 'user_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })

		const guildLocale = await getGuildLocale(guild.id)
		let duration_string = ""
		const stream = await user?.getStream()
		if (!stream) {
			logConsole('twitch', 'stream_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
			duration_string = t(guildLocale, "twitch.notification.offline.duration_unknown")
		} else {
			const duration = new Date().getTime() - stream.startDate.getTime()
			const seconds = Math.floor(duration / 1000)
			const minutes = Math.floor(seconds / 60)
			const hours = Math.floor(minutes / 60)
			// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
			duration_string = `${hours ? hours + "H " : ""}${minutes % 60 ? (minutes % 60) + "M " : ""}${seconds % 60 ? (seconds % 60) + "S" : ""}`
		}

		let content = ""
		if (!streamer.discordUserId) content = t(guildLocale, "twitch.notification.offline.everyone", { streamer: streamerName })
		else content = t(guildLocale, "twitch.notification.offline.everyone_with_mention", { discordId: streamer.discordUserId })

		const embed = new EmbedBuilder()
			.setColor("#6441a5")
			.setAuthor({
				name: t(guildLocale, "twitch.notification.offline.author", { duration: duration_string }),
				iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
			})
			.setTimestamp()

		try {
			const message = await channel.messages.fetch(messageId)
			await message.edit({ content, embeds: [embed] })
		} catch (error) {
			logConsoleError('twitch', 'error_editing_message', { guild: guild.name, streamer: streamerName, id: streamerId }, error as Error)
		}

		const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === streamerId)
		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', 'stop_watching_error', { streamer: streamerName, id: streamerId, guildId }, error as Error)
	}
}

async function generateNotification(guild: Guild, streamerId: string, streamerName: string) {
	const guildProfile = await dbGuild.findOne({ guildId: guild.id })
	if (!guildProfile) {
		logConsole('twitch', 'database_not_exist', { guild: guild.name })
		return { status: "noProfile" }
	}

	const dbData = guildProfile.get("guildTwitch") as GuildTwitch
	if (!dbData.enabled || !dbData.channelId) {
		logConsole('twitch', 'module_disabled', { guild: guild.name })
		return { status: "disabled" }
	}

	const channel = await guild.channels.fetch(dbData.channelId)
	if ((channel?.type !== ChannelType.GuildText && channel?.type !== ChannelType.GuildAnnouncement)) {
		logConsole('twitch', 'channel_not_found', { guild: guild.name, channelId: dbData.channelId })
		return { status: "noChannel" }
	}

	const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
	if (!streamer) {
		logConsole('twitch', 'streamer_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
		return { status: "noStreamer" }
	}

	const user = await twitchClient.users.getUserById(streamerId)
	if (!user) logConsole('twitch', 'user_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })

	const stream = await user?.getStream()
	if (!stream) logConsole('twitch', 'stream_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })

	const guildLocale = await getGuildLocale(guild.id)
	let content = ""
	if (!streamer.discordUserId) content = t(guildLocale, "twitch.notification.online.everyone", { streamer: user?.displayName ?? streamerName })
	else content = t(guildLocale, "twitch.notification.online.everyone_with_mention", { discordId: streamer.discordUserId })

	const embed = new EmbedBuilder()
		.setColor("#6441a5")
		.setTitle(stream?.title ?? t(guildLocale, "twitch.notification.online.title_unknown"))
		.setURL(`https://twitch.tv/${streamerName}`)
		.setAuthor({
			name: t(guildLocale, "twitch.notification.online.author", { streamer: (user?.displayName ?? streamerName).toUpperCase() }),
			iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
		})
		.setDescription(t(guildLocale, "twitch.notification.online.description", { 
			game: stream?.gameName ?? "?", 
			viewers: stream?.viewers.toString() ?? "?" 
		}))
		.setImage(stream?.thumbnailUrl.replace("{width}", "1920").replace("{height}", "1080") ?? "https://assets.help.twitch.tv/article/img/000002222-01a.png")
		.setTimestamp()

	return { status: "ok", guildProfile, dbData, channel, content, embed }
}

export function generateTwitchEmbed(dbData: GuildTwitch, client: Client, guildId: string, locale: Locale) {
	// Récupérer les informations du canal
	let channelInfo = t(locale, "twitch.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, "twitch.common.channel_not_found")
	}

	// Créer l'embed principal
	const embed = new EmbedBuilder()
		.setTitle(t(locale, "twitch.title"))
		.setColor(dbData.enabled ? 0x9146FF : 0x808080)
		.addFields(
			{ name: t(locale, "common.status"), value: dbData.enabled ? t(locale, "twitch.common.enabled") : t(locale, "twitch.common.disabled"), inline: true },
			{ name: t(locale, "common.channel"), value: channelInfo, inline: true },
			{ name: "👥 Streamers", value: t(locale, "twitch.streamers_count", { count: dbData.streamers.length.toString() }), inline: true }
		)
		.setFooter({ text: t(locale, "twitch.managed_by", { bot: client.user?.displayName ?? "Bot" }) })
		.setTimestamp()

	// Boutons première ligne - Toggle et configuration
	const toggleButton = new ButtonBuilder()
		.setCustomId(dbData.enabled ? "twitch_disable" : "twitch_enable")
		.setLabel(dbData.enabled ? t(locale, "common.disable") : t(locale, "common.enable"))
		.setStyle(dbData.enabled ? ButtonStyle.Danger : ButtonStyle.Success)
		.setEmoji(dbData.enabled ? "❌" : "✅")

	const channelButton = new ButtonBuilder()
		.setCustomId("twitch_channel")
		.setLabel(t(locale, "twitch.common.configure_channel"))
		.setStyle(ButtonStyle.Secondary)
		.setEmoji("📺")

	// Boutons seconde ligne - Gestion des streamers
	const listButton = new ButtonBuilder()
		.setCustomId("twitch_streamer_list")
		.setLabel(t(locale, "common.list"))
		.setStyle(ButtonStyle.Secondary)
		.setEmoji("📋")

	const addButton = new ButtonBuilder()
		.setCustomId("twitch_streamer_add")
		.setLabel(t(locale, "common.add"))
		.setStyle(ButtonStyle.Primary)
		.setEmoji("➕")

	const removeButton = new ButtonBuilder()
		.setCustomId("twitch_streamer_remove")
		.setLabel(t(locale, "common.remove"))
		.setStyle(ButtonStyle.Danger)
		.setEmoji("🗑️")

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

	return { embed, components }
}
