import fs from 'node:fs'

import { listAppConfigEntries, upsertAppConfigEntry } from '../../repositories/app-config-repo.js'
import { readJsonFile } from '../../utils/json-file-store.js'

type NormalizeJsonValue<T> = (value: unknown) => T

const configCache = new Map<string, unknown>()
let databaseCacheLoaded = false

export function hasAppConfigEntry(configKey: string, legacyFilePath = '') {
  const key = normalizeConfigKey(configKey)
  if (configCache.has(key)) {
    return true
  }

  return Boolean(legacyFilePath) && fs.existsSync(legacyFilePath)
}

export function readAppConfigEntry<T>({
  configKey,
  legacyFilePath,
  fallback,
  normalize,
}: {
  configKey: string
  legacyFilePath?: string | undefined
  fallback: T | (() => T)
  normalize: NormalizeJsonValue<T>
}): T {
  const key = normalizeConfigKey(configKey)
  if (configCache.has(key)) {
    return normalize(cloneJsonValue(configCache.get(key)))
  }

  if (!databaseCacheLoaded && legacyFilePath) {
    return readJsonFile(legacyFilePath, fallback, normalize)
  }

  return normalize(resolveFallback(fallback))
}

export async function saveAppConfigEntry<T>({
  configKey,
  value,
  legacyFilePath,
  normalize,
}: {
  configKey: string
  value: unknown
  legacyFilePath?: string | undefined
  normalize: NormalizeJsonValue<T>
}): Promise<T> {
  const key = normalizeConfigKey(configKey)
  const normalized = normalize(value)

  await upsertAppConfigEntry({
    configKey: key,
    configJson: normalized,
    sourceFilePath: legacyFilePath,
    migratedFromFile: false,
    timestamp: new Date().toISOString(),
  })

  configCache.set(key, cloneJsonValue(normalized))

  return normalized
}

export async function loadAppConfigCacheFromDatabase() {
  const rows = await listAppConfigEntries()
  configCache.clear()

  for (const row of rows) {
    const key = normalizeConfigKey(row.config_key)
    if (!key) {
      continue
    }

    configCache.set(key, cloneJsonValue(row.config_json))
  }

  databaseCacheLoaded = true
}

export function setAppConfigCacheEntry(configKey: string, value: unknown) {
  const key = normalizeConfigKey(configKey)
  if (!key) {
    return
  }

  configCache.set(key, cloneJsonValue(value))
}

function normalizeConfigKey(value: unknown) {
  return String(value || '').trim()
}

function resolveFallback<T>(fallback: T | (() => T)): T {
  return typeof fallback === 'function' ? (fallback as () => T)() : fallback
}

function cloneJsonValue<T>(value: T): T {
  return JSON.parse(JSON.stringify(value ?? null)) as T
}
