import axios from "axios"
import type { AxiosError } from "axios"
import crypto from "crypto"
import { MessageFlags } from "discord.js"
import type { ChatInputCommandInteraction, ButtonInteraction, Client } from "discord.js"
import type {
	APIResponseData, APIResponseDataError, APIResponseDataVersion,
	ConnectionStatus, GetChallenge, LcdConfig, OpenSession, RequestAuthorization, TokenRequest, TrackAuthorizationProgress
} from "@/types/freebox"
import type { GuildFbx } from "@/types/schemas"
import { t } from "@/utils/i18n"
import { logConsole } from "@/utils/console"

const app: TokenRequest = {
	app_id: "fr.angels-dev.tamiseur",
	app_name: "Bot Tamiseur",
	app_version: process.env.npm_package_version ?? "1.0.0",
	device_name: "Bot Discord NodeJS",
}

export const Core = {
	async Version(host: string) {
		try {
			const res = await axios.get<APIResponseDataVersion>(host + `/api_version`)
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	},
	async Init(host: string, trackId?: string) {
		let request
		if (trackId) request = axios.get<APIResponseData<TrackAuthorizationProgress>>(host + `/api/v8/login/authorize/` + trackId)
		else request = axios.post<APIResponseData<RequestAuthorization>>(host + `/api/v8/login/authorize/`, app)

		try {
			const res = await request
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	}
}

export const Login = {
	async Challenge(host: string) {
		try {
			const res = await axios.get<APIResponseData<GetChallenge>>(host + `/api/v8/login/`)
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	},
	async Session(host: string, password: string) {
		try {
			const res = await axios.post<APIResponseData<OpenSession>>(host + `/api/v8/login/session/`, { app_id: app.app_id, password })
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	}
}

export const Get = {
	async Connection(host: string, sessionToken: string) {
		try {
			const res = await axios.get<APIResponseData<ConnectionStatus>>(host + `/api/v11/connection/`, { headers: { "X-Fbx-App-Auth": sessionToken } })
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	},
	async LcdConfig(host: string, sessionToken: string) {
		try {
			const res = await axios.get<APIResponseData<LcdConfig>>(host + `/api/v8/lcd/config/`, { headers: { "X-Fbx-App-Auth": sessionToken } })
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	}
}

export const Set = {
	async LcdConfig(host: string, sessionToken: string, config: LcdConfig) {
		try {
			const res = await axios.put<APIResponseData<LcdConfig>>(host + `/api/v8/lcd/config/`, config, { headers: { "X-Fbx-App-Auth": sessionToken } })
			return res.data
		} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
	}
}

export async function handleError(error: APIResponseDataError, interaction: ChatInputCommandInteraction | ButtonInteraction, reply = true) {
	if (reply) return await interaction.reply({ content: t(interaction.locale, `freebox.error.${error.error_code}`), flags: MessageFlags.Ephemeral })
	else return await interaction.followUp({ content: t(interaction.locale, `freebox.error.${error.error_code}`), flags: MessageFlags.Ephemeral })
}

// Stockage des timers actifs pour chaque guild
const activeTimers = new Map<string, { morning: NodeJS.Timeout | null, night: NodeJS.Timeout | null }>()

// Gestion du timer LCD
export const Timer = {
	// Fonction pour programmer le timer LCD Freebox
	schedule(client: Client, guildId: string, dbData: GuildFbx) {
		const lcd = dbData.lcd
		if (!lcd?.morningTime || !lcd.nightTime) return

		// Nettoyer les anciens timers pour cette guild
		Timer.clear(guildId)

		const now = new Date()
		const [morningHour, morningMinute] = lcd.morningTime.split(':').map(Number)
		const [nightHour, nightMinute] = lcd.nightTime.split(':').map(Number)

		// Calculer le prochain allumage (matin)
		const nextMorning = new Date(now.getFullYear(), now.getMonth(), now.getDate(), morningHour, morningMinute, 0, 0)
		if (nextMorning <= now) nextMorning.setDate(nextMorning.getDate() + 1)

		// Calculer la prochaine extinction (nuit)
		const nextNight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), nightHour, nightMinute, 0, 0)
		if (nextNight <= now) nextNight.setDate(nextNight.getDate() + 1)

		// Programmer l'allumage le matin
		const morningDelay = nextMorning.getTime() - now.getTime()
		const morningTimer = setTimeout(() => {
			void Timer.controlLeds(guildId, dbData, true)
			// Reprogrammer pour le cycle suivant
			Timer.schedule(client, guildId, dbData)
		}, morningDelay)

		// Programmer l'extinction le soir
		const nightDelay = nextNight.getTime() - now.getTime()
		const nightTimer = setTimeout(() => {
			void Timer.controlLeds(guildId, dbData, false)
			// Note: pas besoin de reprogrammer ici car le timer du matin s'en charge
		}, nightDelay)

		// Stocker les références des timers
		activeTimers.set(guildId, { morning: morningTimer, night: nightTimer })

		logConsole('freebox', 'timer_scheduled', { guildId, nextMorning: nextMorning.toLocaleString(), nextNight: nextNight.toLocaleString() })
	},

	// Fonction utilitaire pour calculer la prochaine occurrence d'une heure donnée
	getNextOccurrence(now: Date, hour: number, minute: number): Date {
		const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0)
		
		// Si l'heure cible est déjà passée aujourd'hui, programmer pour demain
		if (target <= now) target.setDate(target.getDate() + 1)
		return target
	},

	// Fonction pour nettoyer les timers d'une guild
	clear(guildId: string) {
		const timers = activeTimers.get(guildId)
		if (timers) {
			if (timers.morning) clearTimeout(timers.morning)
			if (timers.night) clearTimeout(timers.night)
			activeTimers.delete(guildId)
			logConsole('freebox', 'timers_cleaned', { guildId })
		}
	},

	// Fonction pour nettoyer tous les timers (utile lors de l'arrêt du bot)
	clearAll() {
		for (const [guildId] of activeTimers) {
			Timer.clear(guildId)
		}
		logConsole('freebox', 'all_timers_cleaned')
	},

	// Fonction pour contrôler les LEDs
	async controlLeds(guildId: string, dbDataFbx: GuildFbx, enabled: boolean) {
		if (!dbDataFbx.host || !dbDataFbx.appToken) { logConsole('freebox', 'missing_configuration', { guildId }); return }

		try {
			// Obtenir le challenge
			const challengeData = await Login.Challenge(dbDataFbx.host) as APIResponseData<GetChallenge>
			if (!challengeData.success) { logConsole('freebox', 'challenge_error', { guildId }); return }

			const challenge = challengeData.result.challenge
			if (!challenge) { logConsole('freebox', 'challenge_not_found', { guildId }); return }

			// Créer la session
			const password = crypto.createHmac("sha1", dbDataFbx.appToken).update(challenge).digest("hex")
			const sessionData = await Login.Session(dbDataFbx.host, password) as APIResponseData<OpenSession>
			if (!sessionData.success) { logConsole('freebox', 'session_error', { guildId }); return }

			const sessionToken = sessionData.result.session_token
			if (!sessionToken) { logConsole('freebox', 'session_token_not_found', { guildId }); return }

			// Contrôler les LEDs
			const lcdData = await Set.LcdConfig(dbDataFbx.host, sessionToken, { led_strip_enabled: enabled }) as APIResponseData<LcdConfig>
			if (!lcdData.success) { logConsole('freebox', 'leds_control_error', { guildId }); return }

			logConsole('freebox', 'leds_success', { status: enabled ? 'allumées' : 'éteintes', guildId })
		} catch {
			logConsole('freebox', 'leds_error', { guildId })
		}
	}
}
