/**
 * This file is intended to simulate a database and is only to be used for the FIRST backend lecture.
 * Starting from lecture 2 Prisma must be used.
 * DO NOT COPY THIS TO YOUR OWN PROJECT.
 */

import 'server-only'
import {mkdir, readFile, writeFile} from 'node:fs/promises'
import {randomUUID} from 'node:crypto'
import {stat} from 'fs/promises'

const userStorage =
  process.env.APPDATA ||
  (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + '/.local/share')
const dbPath = `${userStorage ?? './database'}/backend_frameworks_db_simulation`
// eslint-disable-next-line no-console
console.log(`Storing database simulation in ${dbPath}`)

type Id = string | number
interface Persistable {
  id: Id
}

/**
 * Retrieve the content of a single table (modelled using a JSON file).
 *
 * @param tableName The name of the table to read from the filesystem.
 */
export async function getTable<T extends Persistable>(tableName: string): Promise<T[]> {
  const fileName = `${dbPath}/${tableName}`.replaceAll('//', '/')

  if (await exists(fileName)) {
    const data = await readFile(fileName, {encoding: 'utf8'})
    return JSON.parse(data) as T[]
  }

  return []
}

/**
 * Write a table to a (JSON) file.
 *
 * @param tableName The name of the table to write to the filesystem.
 * @param data The data to write.
 */
export async function writeTable<T extends Persistable>(tableName: string, data: T[]): Promise<void> {
  if (!(await exists(dbPath))) {
    await mkdir(dbPath)
  }

  const fileName = `${dbPath}/${tableName}`.replaceAll('//', '/')
  await writeFile(fileName, JSON.stringify(data), {encoding: 'utf8'})
}

/**
 * Retrieve the content of a single row (modelled using a JSON file).
 *
 * @param tableName The name of the table to read from the filesystem.
 * @param id The id of the row to retrieve.
 */
export async function getRow<T extends Persistable>(tableName: string, id: Id): Promise<T> {
  const data = await getTable<T>(tableName)
  const contact = data.find(t => t.id === id)

  if (!contact) {
    throw new Error(`Could not find a record with the id ${id} in the ${tableName} table.`)
  }

  return contact
}

/**
 * Create a new row in the database
 *
 * @param tableName The name of the table where the new data should be added.
 * @param rowData The data for the new row.
 */
export async function createRow<T extends Persistable>(tableName: string, rowData: Omit<T, 'id'>): Promise<T> {
  const data = await getTable<T>(tableName)
  const newObject = {...rowData, id: randomUUID()} as T
  await writeTable<T>(tableName, [...data, newObject])
  return newObject
}

/**
 * Update an existing row in the database
 *
 * @param tableName The name of the table where the new data should be added.
 * @param rowData The data for the new row.
 */
export async function updateRow<T extends Persistable>(tableName: string, rowData: T): Promise<T> {
  const data = await getTable<T>(tableName)
  const updatedData = data.map(x => (x.id === rowData.id ? {...rowData} : x))
  await writeTable<T>(tableName, updatedData)
  return {...rowData}
}

/**
 * Delete a single existing row from the database.
 *
 * @param tableName The name of the table from which the row must be deleted.
 * @param id The id of the row which must be deleted.
 */
export async function deleteRow<T extends Persistable>(tableName: string, id: Id): Promise<void> {
  const data = await getTable<T>(tableName)
  const filteredData = data.filter(x => x.id !== id)
  await writeTable(tableName, filteredData)
}

/**
 * Checks if a file or directory exists at the given file path.
 *
 * @param path The path for which to check if a file or directory exists.
 */
export async function exists(path: string): Promise<boolean> {
  try {
    await stat(path)
  } catch (_error) {
    return false
  }
  return true
}
