import { configMap } from './configMap.js'
import { getRedisKey, hash } from './util.js'
import { logger } from './logger.js'
import * as cache from './cache.js'
import * as redis from './redis.js'
import { Gauge } from 'prom-client'
import { getViteManifests } from './manifests.js'

let latestVersion

const manifestFileEntriesGauge = new Gauge({
  name: 'manifest_file_entries',
  help: 'Number of entries in merged vite manifest (number of all known files)',
  async collect () {
    const version = latestVersion
    this.set({ version }, Object.keys(await getViteManifests({ version })).length)
  },
  labelNames: ['version']
})

const versionUpdateGauge = new Gauge({
  name: 'version_update_event',
  help: 'Timestamp of a version update event',
  labelNames: ['version']
})

export const fetchLatestVersion = async () => {
  const infos = await Promise.all(configMap.urls.map(async baseUrl => {
    try {
      const response = await fetch(new URL('meta.json', baseUrl))
      if (!response.ok) throw new Error()
      const meta = await response.json()
      const version = meta.commitSha || meta.buildDate || meta.version
      if (!version) throw new Error()
      return version
    } catch (err) {
      logger.warn(`[Version] UI container at ${baseUrl} does not have meta.json. Fall back to version hash based on manifest.`)
    }
    const response = await fetch(new URL('manifest.json', baseUrl))
    if (!response.ok) throw new Error(`Cannot fetch manifest.json from ${baseUrl}`)
    const manifest = await response.json()
    return hash(manifest)
  }))
  return `${hash(infos)}${configMap.salt ? `-${configMap.salt}` : ''}`
}

export async function getLatestVersion () {
  if (latestVersion) return latestVersion
  if (redis.isEnabled()) {
    const version = await redis.client.get(getRedisKey({ name: 'latestVersion' }))
    if (version) {
      logger.info(`[Version] Got initial version from redis: '${version}'`)
      versionUpdateGauge.setToCurrentTime({ version })
      return (latestVersion = version)
    }
  }

  await configMap.load()
  const version = await fetchLatestVersion()
  logger.info(`[Version] Fetched initial version: '${version}'`)

  latestVersion = version
  if (redis.isEnabled()) {
    redis.pubClient.publish(getRedisKey({ name: 'updateLatestVersion' }), version)
    await redis.client.set(getRedisKey({ name: 'latestVersion' }), version)
  }

  return version
}

export function registerLatestVersionListener (client) {
  if (!redis.isEnabled()) return

  const key = getRedisKey({ name: 'updateLatestVersion' })
  client.subscribe(key, (errs, count) => logger.info(`[Redis] Subscribed to ${key}.`))
  client.on('message', async (channel, version) => {
    if (channel !== key) return
    if (latestVersion === version) return logger.info(`[Version] Received 'updateLatestVersion' event but already contains that version '${version}'`)
    logger.info(`[Version] Received 'updateLatestVersion' event. Clearing cache. New version: '${version}'`)
    await configMap.load()
    versionUpdateGauge.setToCurrentTime({ version })
    cache.clear()
    latestVersion = version
  })
}

export async function updateVersionProcessor () {
  try {
    logger.info('[Version] Check for new version')
    await configMap.load()

    const [storedVersion, fetchedVersion] = await Promise.all([
      getLatestVersion(),
      fetchLatestVersion()
    ])

    // don't wait for the data, can be done in background
    getViteManifests({ version: fetchedVersion }).then(manifests => {
      manifestFileEntriesGauge.set({ version: fetchedVersion }, Object.keys(manifests).length)
    })

    if (storedVersion === fetchedVersion) {
      logger.info(`[Version] No new version has been found. No update needed. Current version: ${storedVersion}`)
      return fetchedVersion
    }
    logger.info(`[Version] Found new source version. Current version: '${storedVersion}', new version: '${fetchedVersion}'`)
    if (redis.isEnabled()) {
      const prevProcessedVersion = await redis.client.get(getRedisKey({ name: 'prevProcessedVersion' }))
      // that means, that between the previous update processing and this one, there was no version change
      if (prevProcessedVersion === fetchedVersion) {
        logger.info('[Version] publish update to other nodes.')
        redis.pubClient.publish(getRedisKey({ name: 'updateLatestVersion' }), fetchedVersion)
        await redis.client.set(getRedisKey({ name: 'latestVersion' }), fetchedVersion)
        latestVersion = fetchedVersion
      } else {
        logger.info(`[Version] do not execute update yet. Store version ${fetchedVersion} as previous version.`)
        await redis.client.set(getRedisKey({ name: 'prevProcessedVersion' }), fetchedVersion)
      }
    } else {
      versionUpdateGauge.setToCurrentTime({ version: latestVersion })
      // if redis is disabled, this will only be trigger by a setInterval and not from a redis event
      logger.info('[Version] Clear local cache due to version update.')
      cache.clear()
      latestVersion = fetchedVersion
    }
    return latestVersion
  } catch (err) {
    logger.error(`[Version] comparing version is not possible. Error: ${err.message}`)
  }
}