// https://www.sqlitetutorial.net/sqlite-nodejs/
// https://github.com/JoshuaWise/better-sqlite3/blob/HEAD/docs/api.md

export default {
  GetDB: GetDB,
  AddColumn: AddColumn,
  TableInfo: TableInfo,
  Update: Update,
  Delete: Delete,
  CreateTable: CreateTable,
  SelectAll: SelectAll,
  Select: Select,
  Insert: Insert,
  CloseDB: CloseDB,
  runStatement: runStatement,
  sanitizeQuery: sanitizeQuery,
}

import Sqlite from 'better-sqlite3'
import logger from '../utils/logger'
import utils from '../utils/utils'

const debugLog = process.env.NS_SQL_DEBUG_LOG

function sanitizeQuery(val) {
  if (typeof val === 'string') {
    return val.replace(/'/g, '').replace(/;/g, '')
  }
  return val
}

// { asd: 'asd', basd: 4 } => asd = 'asd', basd = 4
function GetSqlQuerry(conditions: any, type: string, joiner?: string) {
  const res = Object.keys(conditions).reduce((acc, key) => {
    const item = conditions[key]
    const conditionKey = sanitizeQuery(key)
    const condition = sanitizeQuery(conditions[key])

    if (typeof item === 'string') {
      acc.push(`${conditionKey} = '${condition}'`)
    } else {
      acc.push(`${conditionKey} = ${condition}`)
    }
    return acc
  }, [])
  if (type === 'where') {
    if (joiner) {
      return res.join(` ${joiner} `)
    } else {
      return res.join(' AND ')
    }
  } else {
    return res.join(', ')
  }
}

// -------------------------------------------------------------------------

function GetDB(path: string): any {
  utils.CreatePath(path)
  const res = new Sqlite(path)
  res.pragma('synchronous = OFF')
  return res
}

function DebugLog(msg) {
  if (debugLog) {
    logger.DebugLog(msg, 'sql', 0)
  }
}

function AddColumn(db: any, table: any, col: any): any {
  try {
    const colName = Object.keys(col)[0]
    const colType = col.type

    const command = `ALTER TABLE ${table} ADD COLUMN ${colName} ${colType}`
    const stmt = PrepareStatement(db, command)

    return stmt.run()
  } catch (err) {
    console.error(err)
  }
}

function TableInfo(db: any, table: any): any {
  try {
    const command = `PRAGMA table_info(${table})`
    const stmt = PrepareStatement(db, command)

    const infoRes = stmt.all()

    const s2 = `SELECT COUNT(*) FROM ${table}`
    const stmt2 = PrepareStatement(db, s2)

    const countRes = stmt2.get()

    return {
      columns: infoRes,
      dataCount: countRes[Object.keys(countRes)[0]],
    }
  } catch (err) {
    console.error(err)
  }
}

function Update(db: any, table: any, newData: any, conditions: any): any {
  try {
    const command = `UPDATE ${table} SET ${GetSqlQuerry(
      newData,
      'set'
    )} WHERE ${GetSqlQuerry(conditions, 'where')}`
    const stmt = PrepareStatement(db, command)

    return stmt.run()
  } catch (err) {
    console.error(err)
  }
}

function Delete(db: any, table: any, conditions: any): any {
  try {
    const command = `DELETE FROM ${table} WHERE ${GetSqlQuerry(
      conditions,
      'where'
    )}`
    const stmt = PrepareStatement(db, command)

    return stmt.run()
  } catch (err) {
    console.error(err)
  }
}

function CreateTable(db: any, name: any, columns: any, foreignKeys: any): any {
  // CREATE TABLE users(pw text PRIMARY KEY NOT NULL, id number, lastIP text, notes text, loginCount
  // number, lastLogin text, lastAccess text
  //
  // FOREIGN KEY(songartist, songalbum) REFERENCES album(albumartist, albumname) )

  try {
    const cols = Object.keys(columns)
      .reduce((acc, key) => {
        const item = columns[key]
        const flags = []
        const toCheck = {
          primary: 'PRIMARY KEY',
          notNull: 'NOT NULL',
          unique: 'UNIQUE',
          autoIncrement: 'AUTOINCREMENT',
          defaultZero: 'DEFAULT 0',
        }
        Object.keys(toCheck).forEach((key) => {
          if (item[key]) {
            flags.push(toCheck[key])
          }
        })

        acc.push(`${key} ${item.type} ${flags.join(' ')}`)
        return acc
      }, [])
      .join(', ')

    const fKeys = []
    if (foreignKeys) {
      foreignKeys.forEach((foreignKey) => {
        const { keysFrom, table, keysTo } = foreignKey
        fKeys.push(
          `, FOREIGN KEY(${keysFrom.join(
            ', '
          )}) REFERENCES ${table}(${keysTo.join(', ')})`
        )
      })
    }

    // IF NOT EXISTS
    const command = `CREATE TABLE ${name}(${cols}${fKeys.join(' ')})`
    const stmt = PrepareStatement(db, command)
    return stmt.run()
  } catch (err) {
    console.error(err)
  }
}

function SelectAll(db: any, from: any): any {
  try {
    const command = `SELECT * from ${from}`

    const stmt = PrepareStatement(db, command)
    return stmt.all()
  } catch (err) {
    console.error(err)
  }
}

// SELECT * FROM MyTable WHERE SomeColumn > LastValue ORDER BY SomeColumn LIMIT 100;
function Select(db: any, from: any, conditions: any, options: any = {}): any {
  const { joiner, limit } = options

  try {
    let command = `SELECT * from ${from} WHERE ${GetSqlQuerry(
      conditions,
      'where',
      joiner
    )}`

    if (!isNaN(limit)) {
      command += ` LIMIT ${limit}`
    }

    const stmt = PrepareStatement(db, command)
    return stmt.all()
  } catch (err) {
    console.error(err)
  }
}

function Insert(db: any, table: any, data: any): any {
  try {
    const cols = Object.keys(data)
      .reduce((acc, key) => {
        acc.push(`${key}`)
        return acc
      }, [])
      .join(', ')

    const values = Object.keys(data)
      .reduce((acc, key) => {
        const item = data[key]
        if (typeof item === 'string') {
          acc.push(`'${item}'`)
        } else {
          acc.push(`${item}`)
        }
        return acc
      }, [])
      .join(', ')

    const command = `INSERT INTO ${table} (${cols}) VALUES (${values})`
    const stmt = PrepareStatement(db, command)

    return stmt.run()
  } catch (err) {
    console.error(err)
  }
}

function runStatement(db: any, command: string, runType?: string): any {
  const stmt = PrepareStatement(db, command)
  if (!runType) {
    return stmt.all()
  } else if (runType === 'run') {
    return stmt.run()
  }
}

function CloseDB(db: any): void {
  db.close((err) => {
    if (err) {
      return console.error(err.message)
    }
    DebugLog('Close the database connection.')
  })
}

// -------------------------------------------------------------------------

function PrepareStatement(db, command) {
  if (!db) {
    throw new Error(
      'DB is undefined in prepare statement! DB action called with undefined db'
    )
  }
  DebugLog(command)
  return db.prepare(command)
}