import fetch from 'node-fetch'
import path from 'path'
import { URL } from 'url'
import { fileCache } from './files.js'
import { config } from './config.js'
import { hash } from './util.js'
import { logger } from './logger.js'

export const loadViteManifests = (() => {
  let cachePromise
  let lastCacheTime
  let lastHash

  async function reload () {
    await config.load()
    // vite manifests contains a set of objects with the vite-manifests
    // from the corresponding registered services
    const viteManifests = await Promise.all(config.urls.map(async url => {
      const { origin } = new URL(url)
      // fetch the manifests
      const result = await fetch(url)
      if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`)
      try {
        const manifest = await result.json()
        for (const file in manifest) {
          logger.debug(`retrieved ${file} from ${url}`)
          manifest[file].meta = manifest[file].meta || {}
          manifest[file].meta.baseUrl = origin
        }
        return manifest
      } catch (err) {
        throw new Error(`Failed to load manifest for url ${result.url}: ${err}`)
      }
    }))

    // combine all manifests by keys. With duplicates, last wins
    const viteManifest = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
    Object.defineProperty(viteManifest, '__hash__', {
      enumerable: false,
      writable: true
    })

    try {
      viteManifest.__hash__ = hash(viteManifests)
    } catch (err) {
      logger.error(`Failed to calculate hash: ${err.message}`)
    }

    return viteManifest
  }

  return function loadViteManifests ({ useCache = true } = {}) {
    const CACHE_TTL = parseInt(process.env.CACHE_TTL)
    const timeElapsed = () => +((+new Date() - lastCacheTime) / 1000).toFixed(2)

    if (!cachePromise || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) {
      cachePromise = reload()
      cachePromise.then(manifests => {
        if (useCache) logger.info(`reloaded manifests after ${timeElapsed()} seconds`)
        // update cache promise
        const newHash = manifests.__hash__
        if (newHash !== lastHash) {
          if (lastHash) {
            const deps = viteManifestToDeps(manifests)
            // asynchronously rewarm the cache
            fileCache.warmUp(manifests, deps)
          }

          lastHash = newHash
        }
      })
      lastCacheTime = +new Date()
    }
    return cachePromise
  }
})()

export function viteToOxManifest (viteManifests) {
  const deps = viteManifestToDeps(viteManifests)
  return Object.values(viteManifests)
    .filter(manifest => Array.isArray(manifest?.meta?.manifests))
    .map(manifest =>
      manifest.meta.manifests.map(oxManifest => {
        const dependencies = deps[manifest.file]
        const data = {
          ...oxManifest,
          path: manifest.file.slice(0, -path.parse(manifest.file).ext.length)
        }
        if (dependencies?.length > 0) data.dependencies = dependencies
        return data
      })
    )
    .flat()
}

export const getOxManifests = (() => {
  let prevHash
  let oxManifestCache
  return async function getOxManifests () {
    const viteManifest = await loadViteManifests()
    if (viteManifest.__hash__ !== prevHash) {
      oxManifestCache = viteToOxManifest(viteManifest)
      prevHash = viteManifest.__hash__
    }
    return oxManifestCache
  }
})()

export function viteManifestToDeps (viteManifest) {
  const deps = {}
  for (const [codePoint, { isEntry, file, imports, css, assets }] of Object.entries(viteManifest)) {
    if (isEntry && codePoint.endsWith('.html')) deps[codePoint] = [file]
    if (Array.isArray(assets)) assets.forEach(asset => { deps[asset] = [] })
    if (Array.isArray(css)) css.forEach(css => { deps[css] = [] })
    deps[file] = []
      .concat(imports?.map(path => viteManifest[path].file))
      .concat(css)
      .concat(assets)
      .filter(Boolean)
  }
  return deps
}

export const getDependencies = (() => {
  let prevHash
  let depCache
  return async function getDependencies () {
    const viteManifest = await loadViteManifests()
    if (viteManifest.__hash__ !== prevHash) {
      depCache = viteManifestToDeps(viteManifest)
      prevHash = viteManifest.__hash__
    }
    return depCache
  }
})()

export async function getCSSDependenciesFor (file) {
  const allDependencies = await getDependencies()
  const dependencies = allDependencies[file] || []
  return dependencies.filter(dep => /\.css/i.test(dep))
}

export const getVersion = (() => {
  let prevHash
  let versionString
  return async function getVersion () {
    const viteManifest = await loadViteManifests()
    if (viteManifest.__hash__ !== prevHash) {
      versionString = `${+new Date()}.${viteManifest.__hash__}`
      prevHash = viteManifest.__hash__
    }
    return versionString
  }
})()