const _ = require('lodash/fp')
const { format, isValid } = require('date-fns/fp')
const { utcToZonedTime } = require('date-fns-tz/fp')

const db = require('./db')
const logger = require('./logger')
const pgp = require('pg-promise')()

const getMachineName = require('./machine-loader').getMachineName
const NUM_RESULTS = 500

/**
 * Get the latest log's timestamp
 *
 * @name getLastSeen
 * @function
 * @async
 *
 * @param {string} deviceId Machine id to get the last timestamp for
 *
 * @returns {date} Last timestamp
 */
function getLastSeen (deviceId) {
  const sql = `select id, timestamp, serial from logs
  where device_id=$1
  order by timestamp desc, serial desc limit 1`
  return db.oneOrNone(sql, [deviceId])
    .then(log => log ? {timestamp: log.timestamp, serial: log.serial, id: log.id} : null)
}

/**
 * Update logs in db
 *
 * @name update
 * @function
 * @async
 *
 * @param {string} deviceId Machine Id to which logs belong to
 * @param {array} logLines Logs to be saved
 *
 * @returns {null}
 */
function update (deviceId, logLines) {
  const cs = new pgp.helpers.ColumnSet([
    'id', 'device_id', 'log_level', 'timestamp', 'serial', 'message'],
  {table: 'logs'})

  const logs = _.map(log => {
    const formatted = {
      id: log.id,
      deviceId: deviceId,
      message: log.msg,
      logLevel: _.contains('error', _.lowerCase(log.msg)) ? 'error' : 'info',
      timestamp: log.timestamp,
      serial: log.serial || 0
    }
    return _.mapKeys(_.snakeCase, formatted)
  }, logLines)
  const sql = pgp.helpers.insert(logs, cs) + 'on conflict do nothing'

  return db.none(sql)
}

function clearOldLogs () {
  const sqls = `delete from logs
    where timestamp < now() - interval '3 days';
    delete from server_logs
    where timestamp < now() - interval '3 days';`
  return db.multi(sqls)
}

function getUnlimitedMachineLogs (deviceId, until = new Date().toISOString()) {
  // Note: sql is a little confusing here, since timestamp is used both as a column
  // and a reserved word, but it works.
  const sql = `select id, log_level, timestamp, message from logs
  where device_id=$1
  and timestamp <= $2
  and timestamp > (timestamp $2 - interval '2 days')
  order by timestamp desc, serial desc`

  return Promise.all([db.any(sql, [ deviceId, until ]), getMachineName(deviceId)])
    .then(([logs, machineName]) => ({
      logs: _.map(_.mapKeys(_.camelCase), logs),
      currentMachine: {deviceId, name: machineName}
    }))
}

function getMachineLogs (deviceId, until = new Date().toISOString(), limit = null, offset = 0) {
  const sql = `select id, log_level, timestamp, message from logs
  where device_id=$1
  and timestamp <= $2
  order by timestamp desc, serial desc
  limit $3
  offset $4`

  return Promise.all([db.any(sql, [ deviceId, until, limit, offset ]), getMachineName(deviceId)])
    .then(([logs, machineName]) => ({
      logs: _.map(_.mapKeys(_.camelCase), logs),
      currentMachine: {deviceId, name: machineName}
    }))
}

function simpleGetMachineLogs (deviceId, from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
  const sql = `select id, log_level, timestamp, message from logs
  where device_id=$1
  and timestamp >= $2
  and timestamp <= $3
  order by timestamp desc, serial desc
  limit $4
  offset $5`

  return db.any(sql, [ deviceId, from, until, limit, offset ])
    .then(_.map(_.mapKeys(_.camelCase)))
}

function logDateFormat (timezone, logs, fields) {
  return _.map(log => {
    const values = _.map(
      field =>
        {
          if (_.isNil(log[field])) return null
          if (!isValid(log[field])) {
            logger.warn(`Tried to convert to ${timezone} timezone the value ${log[field]} and failed. Returning original value...`)
            return log[field]
          }
          const date = utcToZonedTime(timezone, log[field])
          return `${format('yyyy-MM-dd', date)}T${format('HH:mm:ss.SSS', date)}`
        },
        fields
      )
    const fieldsToOverride = _.zipObject(fields, values)
    return {
      ...log,
      ...fieldsToOverride
    }
  }, logs)
}

module.exports = { getUnlimitedMachineLogs, getMachineLogs, simpleGetMachineLogs, update, getLastSeen, clearOldLogs, logDateFormat }
