const { App: SlackApp } = require('@slack/bolt')
const config = require('../config')
const fs = require('fs')
const http = require('http')
const { addReactions, saveGame, setSlackAppClientChatUpdate, parseOr, setSlackUsers } = require('../games/hvacoins/utils')

const temperatureChannelId = 'C034156CE03'
const dailyStandupChannelId = 'C03L533AU3Z'

const pollingMinutes = 5
const pollingPeriod = 1000 * 60 * pollingMinutes

const MAX_POLLS = 3
const HOURS_PER_WINDOW = 2

const colderEmoji = '3d-penguin'
const hotterEmoji = 'heat'
const goodEmoji = 'theworm'

const app = new SlackApp({
    token: config.slackBotToken,
    signingSecret: config.slackSigningSecret,
    appToken: config.slackAppToken,
    socketMode: true
  })
  // app.client.conversations.list({types: 'private_channel'}).then(fetched => {
  //   temperatureChannelId = fetched.channels.filter(channel => channel.name === 'thermo-posting')[0].id
  //   console.log('techThermostatChannelId', temperatureChannelId)
  // })

const pollTriggers = ['!temp', '!temperature', '!imhot', '!imcold', '!im...cold', '!im...hot', '!im...cold?', '!im...hot?', '!imfreezing', '!idonthavemysweater', '!itsdangtoasty', '!itschilly', '!itsdangchilly']
const halfTriggers = ['change temperature', "i'm cold", "i'm hot", 'quack', 'hvacker', '<@U0344TFA7HQ>']

const sendHelp = async (say, prefix) => {
  if (prefix) {
    prefix = prefix + '\n'
  } else {
    prefix = ''
  }

  await say({
    text: prefix +
      `Sending a message matching any of \`${pollTriggers.join('`, `')}\` will start a temperature poll.\n` +
      '\'Hotter\' and \'Colder\' votes offset. E.g. with votes Hotter - 4, Colder - 3, and Content - 2, the temp won\'t change.\n' +
      `At this time I am not capable of actually changing the temperature. Go bug ${users.ThermoController}.`
  })
}

const getMessage = async ({ channel, ts }) => app.client.conversations.history({
  channel: channel,
  latest: ts,
  inclusive: true,
  limit: 1
})

app.event('reaction_added', async ({ event, context, client, say }) => {
  console.log('reaction_added', event)
  for (const listener of reactionListeners) {
    listener({ event, say })
  }
})

const users = parseOr(fs.readFileSync('./users.json', 'utf-8'),
    () => ({}))

const buildSayPrepend = ({ say, prepend }) => async msg => {
  if (typeof(msg) === 'string') {
    return say(prepend + msg)
  }
  return say({
    ...msg,
    text: prepend + msg.text
  })
}

process.once('SIGINT', () => {
  saveGame('SIGINT', true)
  process.exit()
})

let pollHistory = []
const activePolls = {}
const testId = 'U028BMEBWBV_TEST'
let testMode = false
app.event('message', async ({ event, context, client, say }) => {
  if (event.text?.startsWith('!!!')) {
    return
  }
  if (event.subtype !== 'message_changed' && event?.text !== '!') {
    console.log('message.event', {
      ...event,
      userName: users[event.user]
    })
  }
  if (event?.user === users.Admin) {
    if (event.files?.length) {
      const fileName = Math.random().toString()
      const file = fs.createWriteStream(fileName)
      await app.client.files.upload({
        channels: event.channel,
        initial_comment: `Here's your file!`,
        file: createReadStream(path)
      })
      return
    }
    if (event?.text.startsWith('!')) {
      if (testMode) {
        await messageAdmin('Currently in test mode!')
      }
    }
    if (event?.text === '!test') {
      testMode = !testMode
      await messageAdmin(`TestMode: ${testMode} with ID ${testId}`)
    } else if (event?.text === '!notest') {
      testMode = false
      await messageAdmin(`TestMode: ${testMode}`)
    }
    if (testMode) {
      event.user = testId
    }
  }
  for (const listener of messageListeners) {
    listener({ event, say })
  }
  // if (event.user) {
  //   console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
  // }
  if (event.user === users.Admin && event.channel === 'D0347Q4H9FE') {
    if (event.text === '!!kill') {
      saveGame('!!kill', true)
      process.exit(1)
    } else if (event.text === '!!restart') {
      if (Object.entries(activePolls).length === 0) {
        saveGame('!!restart', true)
        process.exit(0)
      } else {
        await messageAdmin('Restart pending poll completion...')
        pendingRestart = true
      }
    }
    if (event.text?.startsWith('!say ') || event.text?.startsWith('!say\n')) {
      await postToTechThermostatChannel(event.text.substring(4).trim().replace('@here', '<!here>'))
      return
    }
  }
  const eventText = event.text?.toLowerCase() || ''

  if (eventText === '!help') {
    await sendHelp(say)
    return
  }

  if (!pollTriggers.includes(eventText)) {
    if (halfTriggers.includes(eventText)) {
      await sendHelp(say, 'It looks like you might want to change the temperature.')
    }
    return
  }
  
  if (event.channel !== temperatureChannelId) {
    return say(`Please request polls in the appropriate channel.`)
  }

  if (activePolls[event.channel]) {
    await postToTechThermostatChannel({ text: "There's already an active poll in this channel!" })
    return
  }
  const now = new Date()
  const windowStart = new Date()
  windowStart.setHours(now.getHours() - HOURS_PER_WINDOW)

  const pollsInWindow = pollHistory.filter(pollTime => pollTime > windowStart)
  const pollText = MAX_POLLS === 1 ? 'poll' : 'polls'
  const hourText = HOURS_PER_WINDOW === 1 ? 'hour' : `${HOURS_PER_WINDOW} hours`

  if (pollsInWindow.length >= MAX_POLLS) {
    await postToTechThermostatChannel({ text: `You have exceeded the limit of ${MAX_POLLS} ${pollText} per ${hourText}!` })
    return
  }

  if (pollHistory.push(now) > MAX_POLLS) {
    [, ...pollHistory] = pollHistory
  }

  activePolls[event.channel] = true
  const pollTs = await startPoll()
  setTimeout(async () => {
    const reactions = await app.client.reactions.get({
      channel: temperatureChannelId,
      timestamp: pollTs,
      full: true
    })

    const reactPosters = {}
    reactions.message.reactions.forEach(r => r.users.forEach(user => {
      reactPosters[user] ??= []
      reactPosters[user].push(r.name)
    }))

    const reactCounts = {}
    Object.entries(reactPosters)
      .filter(([id]) => !users.CANNOT_VOTE?.includes(users[id]))
      .forEach(([id, votes]) => {
        console.log(`VOTES FROM ${id}:`, votes)
        votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].find(emoji => v.startsWith(emoji)))
        if (votes.length === 1) {
          const name = votes[0].replace(/:.*/g, '')
          reactCounts[name] ??= 0
          reactCounts[name] += 1
        }
      })
    console.log('REACT COUNTS', JSON.stringify(reactCounts))

    const contentVotes = reactCounts[goodEmoji] || 0
    let hotterVotes = reactCounts[hotterEmoji] || 0
    let colderVotes = reactCounts[colderEmoji] || 0
    console.log('before contentVotes', contentVotes)
    console.log('before colderVotes', colderVotes)
    console.log('before hotterVotes', hotterVotes)

    if (hotterVotes > colderVotes) {
      hotterVotes -= colderVotes
      colderVotes = 0
    } else if (colderVotes > hotterVotes) {
      colderVotes -= hotterVotes
      hotterVotes = 0
    }
    console.log('after contentVotes', contentVotes)
    console.log('after colderVotes', colderVotes)
    console.log('after hotterVotes', hotterVotes)

    let text
    if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
      if (users.ThermoController === 'someone with hands') {
        text = `@SomeoneWithHands, The people have spoken, and would like to `
      } else {
        text = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
      }
      text += 'raise the temperature, quack.'
      requestTempChange('Hotter')
    } else if (colderVotes > hotterVotes && colderVotes > contentVotes) {
      if (users.ThermoController === 'someone with hands') {
        text = `@SomeoneWithHands, The people have spoken, and would like to `
      } else {
        text = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
      }
      text += 'lower the temperature, quack quack.'
      requestTempChange('Colder')
    } else {
      text = `The people have spoken, and would like to `
      text += 'keep the temperature as-is, quaaack.'
      requestTempChange('Good')
    }

    await postToTechThermostatChannel({ text })
    delete activePolls[event.channel]
    if (pendingRestart && Object.entries(activePolls).length === 0) {
      await messageAdmin('Performing pending restart!')
      saveGame(null, true)
      process.exit(0)
    }
  }, pollingPeriod)
})

let pendingRestart = false

;(async () => {
  await app.start()
  console.log('Slack Bolt has started')
})()

const postToTechThermostatChannel = async optionsOrText => {
  if (optionsOrText === null || typeof optionsOrText !== 'object') {
    optionsOrText = {
      text: optionsOrText
    }
  }
  return app.client.chat.postMessage({ ...optionsOrText, channel: temperatureChannelId })
}

const messageAdmin = async optionsOrText => messageIn(users.Admin, optionsOrText)

const messageIn = async (channel, optionsOrText) => {
  if (optionsOrText === null || typeof optionsOrText !== 'object') {
    optionsOrText = {
      text: optionsOrText
    }
  }
  return app.client.chat.postMessage({ ...optionsOrText, channel })
}

const startPoll = async () => {
  const sent = await postToTechThermostatChannel({
    text: `<!here> Temperature poll requested! In ${pollingMinutes} minutes the temperature will be adjusted.
Pick :${colderEmoji}: if you want it colder, :${hotterEmoji}: if you want it hotter, or :${goodEmoji}: if you like it how it is.
(Note that I can't actually change the temperature yet. Make ${users.ThermoController} do it!)`
  })
  await addReactions({
    app,
    channelId: temperatureChannelId,
    timestamp: sent.ts,
    reactions: [colderEmoji, hotterEmoji, goodEmoji]
  })
  return sent.ts
}

const tempChangeListeners = []
const messageListeners = []
const reactionListeners = []

const requestTempChange = change =>
  tempChangeListeners.forEach(listener => listener(change))

// noinspection HttpUrlsUsage
const encodeData = (key, data) =>
  `<http://${key}ZZZ${Buffer.from(JSON.stringify(data), 'utf-8').toString('base64')}| >`

const decodeData = (key, message) => {
  try {
    const regex = new RegExp(`http://${key}ZZZ[^|]*`)
    let match = message.match(regex)
    if (!match) {
      return match
    }
    match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length
    return JSON.parse(Buffer.from(match, 'base64').toString('utf-8'))
  } catch (e) {
    console.error(e)
    return null
  }
}

const onReaction = listener => reactionListeners.push(listener)

const channelIsIm = async channel => (await app.client.conversations.info({ channel }))?.channel?.is_im

const wasMyMessage = async event => {
  const text = (await app.client.conversations.history({
    channel: event.item.channel,
    latest: event.item.ts,
    limit: 1,
    inclusive: true
  })).messages[0].text

  const decoded = decodeData('commandPayload', text)
  return decoded.event.user === event.user
}

onReaction(async ({ event }) => {
  console.log({ event })
  if (event.reaction === 'x' && (event.user === users.Admin || (await wasMyMessage(event)) || await channelIsIm(event.item.channel))) {
    try {
      await app.client.chat.delete({ channel: event.item.channel, ts: event.item.ts })
    } catch (e) {
    }
  }
})

setSlackAppClientChatUpdate(app.client.chat.update)
setSlackUsers(users)

module.exports = {
  app,
  temperatureChannelId,
  dailyStandupChannelId,
  onAction: app.action,
  getMessage,
  updateMessage: app.client.chat.update,
  postToTechThermostatChannel,
  onTempChangeRequested: listener => tempChangeListeners.push(listener),
  onMessage: listener => messageListeners.push(listener),
  onReaction,
  encodeData,
  decodeData,
  messageAdmin,
  messageIn,
  testMode,
  testId,
  users,
  buildSayPrepend,
  pollTriggers,
  pendingRestart
}
