// Owns the single discord.js Client instance for this process: lifecycle
// (start/stop/status) and slash-command registration/dispatch. Command
// definitions themselves live in ./commands — this file only wires them up.
const { Client, GatewayIntentBits, REST, Routes } = require('discord.js')

const createLogger = require('../utils/logger')
const commands = require('./commands')
const messageFilter = require('./messageFilter')
const scheduler = require('../scheduler/scheduler')
const roleMenuHandler = require('./roleMenuHandler')
const { handleGuildMemberAdd } = require('./guildMemberAdd')
const { handleGuildMemberRemove } = require('./guildMemberRemove')
const inviteTracker = require('./inviteTracker')
const tempRoleSweeper = require('../roles/tempRoleSweeper')
const inviteScheduler = require('../invites/inviteScheduler')

const log = createLogger('discord')

let client = null
let guildId = null
let status = 'disconnected' // disconnected | connecting | connected | error
let statusDetail = null
let lastConnectedAt = null

async function registerCommands(applicationId, targetGuildId) {
  const rest = new REST({ version: '10' }).setToken(client.token)
  await rest.put(Routes.applicationGuildCommands(applicationId, targetGuildId), {
    body: commands.all.map((c) => c.data),
  })
  log.info('registered guild slash commands', { guildId: targetGuildId, count: commands.all.length })
}

async function stop() {
  if (!client) {
    status = 'disconnected'
    statusDetail = null
    return
  }
  scheduler.stop()
  tempRoleSweeper.stop()
  inviteScheduler.stop()
  try {
    await client.destroy()
  } catch (err) {
    log.warn('error while destroying client', { message: err.message })
  }
  client = null
  status = 'disconnected'
  statusDetail = null
  log.info('discord client disconnected')
}

// Post-login startup: register commands and start the background workers. A
// failure here leaves the client connected but flags an error status.
async function onReady() {
  try {
    await registerCommands(client.application.id, guildId)
    await scheduler.start(client)
    tempRoleSweeper.start(client)
    inviteScheduler.start(client, guildId)
    await inviteTracker.prime(client, guildId)
    status = 'connected'
    statusDetail = null
    lastConnectedAt = new Date()
    log.info('discord client ready', { user: client.user?.tag, guildId })
  } catch (err) {
    status = 'error'
    statusDetail = `startup failed: ${err.message}`
    log.error('post-login startup failed (commands/scheduler/temp-roles/invites)', { message: err.message })
  }
}

// Route an interaction: role-menu handler first, then chat-input slash commands.
async function onInteractionCreate(interaction) {
  if (await roleMenuHandler.handleInteraction(interaction)) return
  if (!interaction.isChatInputCommand()) return
  const command = commands.get(interaction.commandName)
  if (!command) return
  try {
    await command.execute(interaction)
  } catch (err) {
    log.error('command execution failed', { command: interaction.commandName, message: err.message })
    const payload = { content: 'Something went wrong running that command.', ephemeral: true }
    if (interaction.replied || interaction.deferred) await interaction.followUp(payload)
    else await interaction.reply(payload)
  }
}

// start({ token, guildId }) — (re)connects. Always stops any existing client
// first so re-saving config or toggling Enabled off/on is idempotent.
async function start({ token, guildId: gid }) {
  await stop()
  guildId = gid
  status = 'connecting'
  statusDetail = null

  // GuildMessages + MessageContent (Phase 3, filter) and GuildMembers
  // (Phase 5, auto-role + bulk role ops) are all privileged — must be enabled
  // in the Discord Developer Portal, see the Phase 1 setup notes. GuildInvites
  // (Phase 6b, invite-usage attribution) is NOT privileged — no portal toggle.
  client = new Client({
    intents: [
      GatewayIntentBits.Guilds,
      GatewayIntentBits.GuildMessages,
      GatewayIntentBits.MessageContent,
      GatewayIntentBits.GuildMembers,
      GatewayIntentBits.GuildInvites,
    ],
  })

  client.once('ready', onReady)
  client.on('interactionCreate', onInteractionCreate)
  client.on('messageCreate', messageFilter.handleMessageCreate)
  client.on('guildMemberAdd', handleGuildMemberAdd)
  client.on('guildMemberRemove', handleGuildMemberRemove)
  // Keep the invite-use cache fresh so guildMemberAdd can attribute joins.
  client.on('inviteCreate', inviteTracker.onInviteCreate)
  client.on('inviteDelete', inviteTracker.onInviteDelete)

  client.on('error', (err) => {
    status = 'error'
    statusDetail = err.message
    log.error('discord client error', { message: err.message })
  })

  try {
    await client.login(token)
  } catch (err) {
    status = 'error'
    statusDetail = err.message
    client = null
    log.error('discord login failed', { message: err.message })
    throw err
  }
}

function getStatus() {
  return { status, statusDetail, guildId, lastConnectedAt }
}

// For code that needs the live client + which guild it's connected to (the
// /internal/announce handler, slash commands already get both from the
// interaction itself so they don't need this). Returns null if disconnected.
function getConnection() {
  if (!client || status !== 'connected') return null
  return { client, guildId }
}

module.exports = { start, stop, getStatus, getConnection }
