// Check if ~/.apptile-cli exists. If it doesn't create it and add workspaces table.

import DatabaseConstructor, {Database as SqliteDB} from 'better-sqlite3';
import os from 'os';
import path from 'path';
import {mkdirSync} from 'fs';
import {DataBase} from '../ui/src/types';

const homeDir = os.homedir();
try {
  mkdirSync(path.resolve(homeDir, '.apptile-cli'));
} catch (err: any) {
  if (err?.code === 'EEXIST') {
    console.log("config folder already exists");
  } else {
    throw err;
  }
}
const dbFilePath = path.resolve(homeDir, '.apptile-cli/db.sqlite');
const db: SqliteDB = new DatabaseConstructor(dbFilePath, { verbose: console.log });

// migrations
db.exec(`CREATE TABLE IF NOT EXISTS workspaces (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    location VARCHAR(255) NOT NULL
  );
`);

db.exec(`
  CREATE TABLE IF NOT EXISTS openapps (
    appId VARCHAR(255) PRIMARY KEY,
    workspaceId INTEGER,
    repoPath VARCHAR(255)
  );
`);

db.exec(`CREATE TABLE IF NOT EXISTS metro (
    pid INTEGER PRIMARY KEY,
    repoPath VARCHAR(255)
  );
`);

export type MetroProc = {
  pid: number;
  repoPath: string;
};

function narrowMetroProc(raw: any): raw is MetroProc {
  try {
    return (typeof raw.pid === "number") &&
      (typeof raw.repoPath === "string");
  } catch (err) {
    console.error("Failed to narrow metro proc");
    return false;
  }
}

function narrowMetroProcArray(raw: any): raw is MetroProc[] {
  try {
    let result = true;
    for (let i = 0; i < raw.length; ++i) {
      result = result && narrowMetroProc(raw[i]);
    }
    return result;
  } catch (err) {
    console.error("Failed to validate metroProc array");
    return false;
  }
}

const delMetroProc = db.prepare(`DELETE FROM metro WHERE repoPath=?`);
export function deleteMetroProc(repoPath: string) {
  delMetroProc.run(repoPath);
}

const selectMetroProc = db.prepare(`SELECT * FROM metro`);
export function getCurrentMetroProc(): null|MetroProc[] {
  let procs = selectMetroProc.all();
  if (narrowMetroProcArray(procs)) {
    return procs;
  } else {
    return null;
  }
}

const insertMetroProc = db.prepare(`INSERT INTO metro (pid, repoPath) VALUES (?,?)`);
export function createMetroProc(pid: number, fullPath: string) {
  insertMetroProc.run(pid.toFixed(0), fullPath);
}

const insertWorkspace = db.prepare(`INSERT INTO workspaces (location)
               VALUES (?)`);
export function createWorkspace(location: string ): void {
  try {
    insertWorkspace.run(location);
  } catch (err) {
    console.error("Failed to create link", err);
  }
}

const removeWorkspace = db.prepare(`DELETE FROM workspaces WHERE id = ?`);
export function deleteLink(id: number) {
  removeWorkspace.run(id);
}

const getAllWorkspaces = db.prepare(`SELECT * FROM workspaces`);
export function getWorkspaces(): DataBase.Workspace[]|null {
  const workspaces = getAllWorkspaces.all();
  
  if (DataBase.narrowWorkspaceArray(workspaces)) {
    return workspaces;
  } else {
    console.error("Invalid link found in table", workspaces);
    return null;
  }
}

const findWorkspace = db.prepare(`SELECT * FROM workspaces WHERE id = ?`);
export function getWorkspace(id: number): DataBase.Workspace|null {
  const workspace = findWorkspace.get(id.toFixed(0));
  if (DataBase.narrowWorkspace(workspace)) {
    return workspace;
  } else {
    console.error("Invalid workspace found in table", workspace);
    return null;
  }
}

const findWorkspaceByLocation = db.prepare(`SELECT * FROM workspaces WHERE location = ?`);
export function getWorkspaceByLocation(location: string): DataBase.Workspace|null {
  const workspace = findWorkspaceByLocation.get(location);
  if (DataBase.narrowWorkspace(workspace)) {
    return workspace;
  } else {
    console.error("Invalid workspace found in table", workspace);
    return null;
  }
}

function narrowOpenedApp(result: any): result is DataBase.OpenedApp {
  return (
    (result) &&
    (typeof (result.appId) === "string") &&
    (typeof (result.workspaceId === "string")) &&
    (typeof (result.repoPath) === "string") && 
    (!!result.repoPath)
  );
}

const insertOrUpdateOpenapps = db.prepare(`INSERT INTO openapps (workspaceId, appId, repoPath) 
  VALUES (?, ?, ?)
  ON CONFLICT(appId)
  DO UPDATE SET 
    workspaceId = excluded.workspaceId,
    repoPath = excluded.repoPath
  `);
export function insertOrUpdateOpenapp(workspaceId: string, appId: string, repoPath: string) {
  insertOrUpdateOpenapps.run(workspaceId, appId, repoPath);
}

const findOpenApp = db.prepare(`SELECT * FROM openapps WHERE appId = ?`);
export function getOpenApp(appId: string): DataBase.OpenedApp|null{
  const openedAppEntry = findOpenApp.get(appId);
  if (narrowOpenedApp(openedAppEntry)) {
    return openedAppEntry;
  } else {
    return null;
  }
}

const deleteAppByLocationQuery = db.prepare(`DELETE FROM openapps WHERE repoPath = ?`);
export function closeAppByLocation(repoPath: string) {
  return deleteAppByLocationQuery.run(repoPath);
}

export default db;
