import dotenv from "dotenv";
import {
  Client,
  GatewayIntentBits,
  Collection,
  Events,
  BaseInteraction,
  SlashCommandBuilder,
} from "discord.js";
import { Shoukaku, Connectors, NodeOption, ShoukakuOptions } from "shoukaku";
import logger from "./utils/logger.js"; // Add .js extension
import fs from "fs";
import path from "path";
import { fileURLToPath } from 'url'; // Needed for __dirname in ES Modules

// Get __dirname equivalent in ES Modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Define Command structure
interface Command {
  data: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">;
  execute: (_interaction: BaseInteraction, _client: BotClient) => Promise<void>;
}

// Define Event structure
interface BotEvent {
  name: string;
  once?: boolean;
  execute: (..._args: any[]) => void;
}

// Extend the discord.js Client class to include custom properties
export interface BotClient extends Client {
  commands: Collection<string, Command>;
  shoukaku: Shoukaku;
}

// --- Setup ---
dotenv.config();

// Validate essential environment variables
if (!process.env.DISCORD_TOKEN) {
  logger.error("DISCORD_TOKEN is missing in the .env file!");
  process.exit(1);
}
if (!process.env.LAVALINK_HOST || !process.env.LAVALINK_PORT || !process.env.LAVALINK_PASSWORD) {
  logger.warn(
    "Lavalink connection details (HOST, PORT, PASSWORD) are missing or incomplete in .env. Music functionality will be limited.",
  );
}

// Create a new Discord _client instance with necessary intents
const _client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildVoiceStates,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
}) as BotClient;

// Define Shoukaku nodes
const Nodes: NodeOption[] = [
  {
    name: process.env.LAVALINK_NAME || "lavalink-node-1",
    url: `${process.env.LAVALINK_HOST || "localhost"}:${process.env.LAVALINK_PORT || 2333}`,
    auth: process.env.LAVALINK_PASSWORD || "youshallnotpass",
    secure: process.env.LAVALINK_SECURE === "true",
  },
];

// Shoukaku _options
const shoukakuOptions: ShoukakuOptions = {
  moveOnDisconnect: false,
  resume: true,
  reconnectTries: 3,
  reconnectInterval: 5000,
};

// Initialize Shoukaku
_client.shoukaku = new Shoukaku(new Connectors.DiscordJS(_client), Nodes, shoukakuOptions);

// Show the actual Lavalink connection details (without exposing the actual password)
logger.info(
  `Lavalink connection configured to: ${process.env.LAVALINK_HOST || "localhost"}:${process.env.LAVALINK_PORT || 2333} (Password: ${process.env.LAVALINK_PASSWORD ? "[SET]" : "[NOT SET]"})`,
);

// Collections for commands
_client.commands = new Collection<string, Command>();

// --- Command Loading ---
const commandsPath = path.join(__dirname, "commands");
// Read .js files instead of .ts after compilation
const commandFiles = fs.readdirSync(commandsPath).filter((file: string) => file.endsWith(".js"));

const loadCommands = async () => {
  for (const file of commandFiles) {
    const filePath = path.join(commandsPath, file);
    try {
      // Use dynamic import with file:// protocol for ES Modules
      const fileUrl = new URL(`file://${filePath}`).href;
      const commandModule = await import(fileUrl);
      const command: Command = commandModule.default || commandModule;

      if (command && typeof command === "object" && "data" in command && "execute" in command) {
        _client.commands.set(command.data.name, command);
        logger.info(`Loaded command: ${command.data.name}`);
      } else {
        logger.warn(
          `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property or is not structured correctly.`,
        );
      }
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error(`Error loading command at ${filePath}: ${errorMessage}`, error);
    }
  }
};

// --- Event Handling ---
const eventsPath = path.join(__dirname, "events");
// Read .js files instead of .ts after compilation
const eventFiles = fs.readdirSync(eventsPath).filter((file: string) => file.endsWith(".js"));

const loadEvents = async () => {
  for (const file of eventFiles) {
    const filePath = path.join(eventsPath, file);
    try {
      // Use dynamic import with file:// protocol for ES Modules
      const fileUrl = new URL(`file://${filePath}`).href;
      const eventModule = await import(fileUrl);
      const event: BotEvent = eventModule.default || eventModule;

      if (event && typeof event === "object" && "name" in event && "execute" in event) {
        if (event.once) {
          _client.once(event.name, (..._args: any[]) => event.execute(..._args, _client));
          logger.info(`Loaded event ${event.name} (once)`);
        } else {
          _client.on(event.name, (..._args: any[]) => event.execute(..._args, _client));
          logger.info(`Loaded event ${event.name}`);
        }
      } else {
        logger.warn(
          `[WARNING] The event at ${filePath} is missing a required "name" or "execute" property or is not structured correctly.`,
        );
      }
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error(`Error loading event at ${filePath}: ${errorMessage}`, error);
    }
  }
};

// --- Shoukaku Event Handling ---
_client.shoukaku.on("ready", (name: string) =>
  logger.info(`Lavalink Node: ${name} is now connected`),
);
_client.shoukaku.on("error", (name: string, error: Error) =>
  logger.error(`Lavalink Node: ${name} emitted an error: ${error.message}`),
);
_client.shoukaku.on("close", (name: string, code: number, reason: string | undefined) =>
  logger.warn(`Lavalink Node: ${name} closed with code ${code}. Reason: ${reason || "No reason"}`),
);
_client.shoukaku.on("disconnect", (name: string, count: number) => {
  logger.warn(
    `Lavalink Node: ${name} disconnected. ${count} players were disconnected from this node.`,
  );
});

// --- Main Execution ---
async function main() {
  await loadCommands();
  await loadEvents();

  // Log in to Discord with your _client's token
  try {
    await _client.login(process.env.DISCORD_TOKEN);
    logger.info("Successfully logged in to Discord.");
  } catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    logger.error(`Failed to log in: ${errorMessage}`);
    process.exit(1);
  }
}

main().catch((error) => {
  const errorMessage = error instanceof Error ? error.message : String(error);
  logger.error(`Error during bot initialization: ${errorMessage}`, error);
  process.exit(1);
});

// Basic error handling
process.on("unhandledRejection", (reason: unknown, promise: Promise<any>) => {
  const reasonMessage = reason instanceof Error ? reason.message : String(reason);
  logger.error("Unhandled promise rejection:", { reason: reasonMessage, promise });
});
process.on("uncaughtException", (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => {
  logger.error(`Uncaught exception: ${error.message}`, { error, origin });
});
