import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, GatewayIntentBits, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js";
import { CommandType } from "../types/commandTypes";
import fs from 'fs'
import { config } from "../configuration";
import { logger } from "../logger";
import { JellyfinHandler } from "../jellyfin/handler";



export class ExtendedClient extends Client {
  private eventFilePath = `${__dirname}/../events`
  private commandFilePath = `${__dirname}/../commands`
  private jellyfin: JellyfinHandler
  public commands: Collection<string, CommandType> = new Collection()
  private announcementChannels: Collection<string, TextChannel> = new Collection //guildId to TextChannel
  public constructor(jf: JellyfinHandler) {
    const intents: IntentsBitField = new IntentsBitField()
    intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates)
    const options: ClientOptions = { intents }
    super(options)
    this.jellyfin = jf
  }
  public async start() {
    if (process.env.NODE_ENV === 'test') return
    const promises: Promise<any>[] = []
    promises.push(this.registerSlashCommands())
    promises.push(this.registerEventCallback())
    Promise.all(promises).then(() => {
      this.login(config.bot.token)
    })
    logger.info(`Connected with ${await this.jellyfin.ServerName()}`)
  }
  private async importFile(filepath: string): Promise<any> {
    logger.debug(`Importing ${filepath}`)
    const imported = await import(filepath)
    logger.debug(`Imported ${JSON.stringify(imported)}`)
    return imported.default ?? imported
  }
  public async registerCommands(cmds: ApplicationCommandDataResolvable[], guildIds: Collection<Snowflake, Guild>) {
    if (guildIds) {
      guildIds.forEach(guild => {
        this.guilds.cache.get(guild.id)?.commands.set(cmds)
        logger.info(`Registering commands to ${guild.name}|${guild.id}`)
      })
    } else {
      this.application?.commands.set(cmds)
      logger.info(`Registering global commands`)
    }
    return
  }
  public async registerSlashCommands(): Promise<void> {
    try {
      const slashCommands: ApplicationCommandDataResolvable[] = []
      const commandFiles = fs.readdirSync(this.commandFilePath).filter(file => file.endsWith('.ts') || file.endsWith('.js'))
      for (const commandFile of commandFiles) {
        const filePath = `${this.commandFilePath}/${commandFile}`
        const command = await this.importFile(filePath)
        logger.debug(JSON.stringify(command))
        if (!command.name) return
        this.commands.set(command.name, command)
        slashCommands.push(command)
      }
      this.on("ready", (client: Client) => {
        //logger.info(`Ready processing ${JSON.stringify(client)}`)
        logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`)
        const guilds = client.guilds.cache

        this.registerCommands(slashCommands, guilds)
        this.cacheUsers(guilds)
        this.cacheAnnouncementServer(guilds)
      })
    } catch (error) {
      logger.info(`Error refreshing slash commands: ${error}`)
    }
  }
  private async cacheAnnouncementServer(guilds: Collection<Snowflake, Guild>) {
    for (const guild of guilds.values()) {
      const channels: TextChannel[] = <TextChannel[]>(await guild.channels.fetch())
        ?.filter(channel => channel!.id === config.bot.announcement_channel_id)
        .map((value, _) => value)

      if (!channels || channels.length != 1) {
        logger.error(`Could not find announcement channel for guild ${guild.name} with guildId ${guild.id}. Found ${channels}`)
        continue
      }
      logger.info(`Fetched announcement channel: ${JSON.stringify(channels[0])}`)
      this.announcementChannels.set(guild.id, channels[0])
    }
  }
  public getAnnouncementChannelForGuild(guildId: string): TextChannel {
    return this.announcementChannels.get(guildId)! //we set the channel by ourselves only if we find one, I think this is sage (mark my words)
  }
  public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
    guilds.forEach((guild: Guild, id: Snowflake) => {
      logger.info(`Fetching members for ${guild.name}|${id}`)
      guild.members.fetch()
      logger.info(`Fetched: ${guild.memberCount} members`)
    })
  }
  public async registerEventCallback() {
    try {
      const eventFiles = fs.readdirSync(this.eventFilePath).filter(file => file.endsWith('.ts') || file.endsWith('.js'));
      for (const file of eventFiles) {
        const filePath = `${this.eventFilePath}/${file}`
        const event = await this.importFile(filePath)
        if (event.once) {
          logger.info(`Registering once ${file}`)
          this.once(event.name, (...args: any[]) => event.execute(...args))
        }
        else {
          logger.info(`Registering on ${file}`)
          this.on(event.name, (...args: any[]) => event.execute(...args))
        }
      }
      logger.info(`Registered event names ${this.eventNames()}`)
    } catch (error) {
      logger.error(error)
    }
  }
}
