import { createWritable } from './files.js'
import { logger } from './logger.js'
import * as redis from './redis.js'
import { getRedisKey } from './util.js'
import { Gauge } from 'prom-client'

const cache = {}

export const fileCacheSizeGauge = new Gauge({
  name: 'file_cache_size',
  help: 'Number of entries in file cache'
})

export function set (key, value) {
  logger.debug(`[Cache] Set ${key}`)
  if (cache[key] === value) return
  cache[key] = value
  if (redis.isEnabled()) {
    return redis.client.set(key, value)
  }
}

export async function clear () {
  for (const prop of Object.getOwnPropertyNames(cache)) {
    delete cache[prop]
  }
  fileCacheSizeGauge.reset()
}

export function get (key, fallback) {
  if (cache[key]) {
    logger.debug(`[Cache] Resolve from memory: ${key}`)
    return cache[key]
  }

  const promise = (async () => {
    if (redis.isEnabled()) {
      let result = await redis.client.get(key)
      if (result) {
        logger.debug(`[Cache] Resolve from redis: ${key}`)
        result = JSON.parse(result)
        cache[key] = result
        return result
      }
    }

    if (!fallback) return

    const fallbackResult = await fallback()
    if (fallbackResult) {
      logger.debug(`[Cache] Found a getter for: ${key}`)
      cache[key] = fallbackResult
      if (redis.isEnabled()) redis.client.set(key, JSON.stringify(fallbackResult))
    }
    return fallbackResult
  })()

  cache[key] = promise

  return promise
}

export function getFile ({ name, version }, fallback) {
  const key = getRedisKey({ version, name })

  // try to get the file synchronously.
  const data = cache[key]
  if (data) {
    logger.debug(`[Cache] Resolve file from memory: ${key}`)
    return data
  }

  // if synchronously does not work, store the async promise for further requests
  const promise = (async () => {
    const bodyKey = getRedisKey({ version, name: `${name}:body` })
    const metaKey = getRedisKey({ version, name: `${name}:meta` })

    if (redis.isEnabled()) {
      const [body, meta = '{}'] = await Promise.all([
        redis.client.getBuffer(bodyKey),
        redis.client.get(metaKey)
      ])

      if (body) {
        logger.debug(`[Cache] Resolve file from redis: ${key}`)
        fileCacheSizeGauge.inc()
        return (cache[key] = { body, ...JSON.parse(meta) })
      }
    }

    const dataFromServer = await fallback({ version, name }).catch(err => {
      if (err.status !== 404) delete cache[key]
      throw err
    })

    if (redis.isEnabled()) {
      logger.debug(`[Cache] Store file in redis: ${key}`)
      const { body, ...rest } = dataFromServer
      redis.client.set(bodyKey, createWritable(body))
      redis.client.set(metaKey, JSON.stringify(rest))
    }

    // overwrite cache with synchronous data
    logger.debug(`[Cache] Store file in memory: ${key}`)
    fileCacheSizeGauge.inc()
    return (cache[key] = dataFromServer)
  })()

  // temporary set to promise
  cache[key] = promise
  return promise
}