import sqlite from "sqlite3";
import bcrypt from "bcryptjs";
import { PHASE_PRODUCTION_BUILD } from "next/dist/shared/lib/constants";

export type AliasEntry = { id: number, address: string, alias: string, pending: boolean, temporary: boolean };
export type AliasRequestEntry = { key: string, address: string, alias: string, expires: number };
export type ApiKeyEntry = { id: number, address: string, label: string, token: string };

export const database = (type: 'credentials' | 'aliases') => {
  if (process.env.NEXT_PHASE != PHASE_PRODUCTION_BUILD) {
    for (const v of ["CREDENTIALS_DB_PATH", "ALIASES_DB_PATH"]) {
      if (!process.env[v]) {
        throw `$${v} not provided; unable to connect to database`;
      }
    }
  }

  const dbPath = type == 'credentials'
    ? process.env["CREDENTIALS_DB_PATH"]
    : process.env["ALIASES_DB_PATH"];

  return new sqlite.Database(dbPath!, (err: any) => {
    if (err && process.env.NEXT_PHASE != PHASE_PRODUCTION_BUILD) throw err;
  });
};

export function validateCredentials(email: string, password: string) {
  const db = database('credentials');

  return new Promise<boolean>((resolve, reject) => {
    db.get(
      "SELECT key, value FROM passwords WHERE key = ?",
      email,
      async (err, row: any) => {
        db.close(); // We don't need this anymore

        if (err) return reject(err);
        if (!row) return resolve(false);

        try {
          const hash: string = row.value.replace("bcrypt:", "");
          const isValid = await bcrypt.compare(password, hash);
          resolve(isValid);
        } catch (e) {
          reject(e);
        }
      }
    );
  });
}

export function createUserEntry(email: string, pass: string) {
  return new Promise<void>(async (resolve, reject) => {
    const db = database('credentials');
    const hash = 'bcrypt:' + await bcrypt.hash(pass, 10);

    db.run("INSERT INTO passwords (key, value) VALUES ($1, $2)",
      { 1: email, 2: hash },
      async (err: any, res: any) => {
        db.close();
        if (err) return reject(err);
        resolve();
      });
  });
}

export function setUserPassword(email: string, newPass: string) {
  return new Promise<void>(async (resolve, reject) => {
    const db = database('credentials');
    const hash = 'bcrypt:' + await bcrypt.hash(newPass, 10);

    db.run("UPDATE passwords SET value = $1 WHERE key = $2",
      { 1: hash, 2: email },
      async (err: any, res: any) => {
        db.close();
        if (err) return reject(err);
        resolve();
      });
  });
}

export function getAllAliases() {
  return new Promise<AliasEntry[]>(async (resolve, reject) => {
    const db = database('aliases');

    db.all("SELECT id, address, alias, pending, temporary FROM aliases", (err, res: any[]) => {
      db.close();
      if (err) return reject(err);
      resolve(res.map((data) => ({
        ...data,
        pending: !!data.pending,
        temporary: !!data.temporary,
      })));
    });
  });
}

export function getUserAliases(email: string, tempAliases?: boolean) {
  return new Promise<AliasEntry[]>(async (resolve, reject) => {
    const db = database('aliases');

    db.all(
      "SELECT id, address, alias, pending, temporary FROM aliases WHERE address = ?1 " + (typeof tempAliases != 'undefined' ? "AND temporary = ?2" : ""),
      {
        1: email,
        2: typeof tempAliases == "boolean" ? (tempAliases ? 1 : 0) : undefined,
      },
      (err, res: any[]) => {
        db.close();
        if (err) return reject(err);
        resolve(res.map((data) => ({
          ...data,
          pending: !!data.pending,
          temporary: !!data.temporary,
        })));
      });
  });
}

export function getAlias(alias: string) {
  return new Promise<AliasEntry | undefined>(async (resolve, reject) => {
    const db = database('aliases');

    db.get("SELECT id, address, alias, pending, temporary FROM aliases WHERE alias = ?", alias, (err, res: AliasEntry) => {
      db.close();
      if (err) return reject(err);
      if (!res) return resolve(undefined);
      resolve({
        ...res,
        pending: !!res.pending,
        temporary: !!res.temporary,
      });
    });
  });
}

export function isAliasAvailable(email: string, searchTempRequests: boolean = false) {
  return new Promise<boolean>((resolve, reject) => {
    const db = database('aliases');
    db.get('SELECT id FROM aliases WHERE alias = ?', email.toLowerCase(), (err, res) => {
      if (!searchTempRequests || err) db.close();
      if (err) return reject(err);
      if (res != undefined) return resolve(false);

      const authDb = database('credentials');
      authDb.get('SELECT key FROM passwords WHERE key = ?', email.toLowerCase(), (err, res) => {
        authDb.close();
        if (err) return reject(err);

        if (!searchTempRequests) {
          return resolve(res == undefined);
        } else {
          db.get('SELECT key FROM temp_alias_requests WHERE alias = ?', email.toLowerCase(), (err, res) => {
            db.close();
            if (err) return reject(err);

            return resolve(res == undefined);
          });
        }
      });
    });
  });
}

export function createAliasEntry(user: string, alias: string, pending: boolean, temporary: boolean = false) {
  return new Promise<number>(async (resolve, reject) => {
    const db = database('aliases');

    db.run(
      "INSERT INTO aliases (address, alias, pending, temporary) VALUES (?1, ?2, ?3, ?4)",
      {
        1: user,
        2: alias,
        3: pending ? 1 : 0,
        4: temporary ? 1 : 0,
      },
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve(this.lastID);
      });
  });
}

export function createTempAliasRequestEntry(key: string, account: string, alias: string, expires: number) {
  return new Promise<number>(async (resolve, reject) => {
    const db = database('aliases');

    db.run(
      "INSERT INTO temp_alias_requests (key, address, alias, expires) VALUES (?1, ?2, ?3, ?4)",
      {
        1: key,
        2: account,
        3: alias,
        4: expires,
      },
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve(this.lastID);
      });
  });
}

export function getTempAliasRequestEntry(key: string) {
  return new Promise<AliasRequestEntry | undefined>(async (resolve, reject) => {
    const db = database('aliases');

    db.get(
      "SELECT key, address, alias, expires FROM temp_alias_requests WHERE key = ?",
      key,
      function (err, res: any) {
        db.close();
        if (err) return reject(err);
        resolve(res);
      });
  });
}

export function deleteTempAliasRequestEntry(key: string) {
  return new Promise<void>(async (resolve, reject) => {
    const db = database('aliases');

    db.run(
      "DELETE FROM temp_alias_requests WHERE key = ?",
      key,
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve();
      });
  });
}

export function approveAliasEntry(alias: string) {
  return new Promise<void>(async (resolve, reject) => {
    const db = database('aliases');

    db.run(
      "UPDATE aliases SET pending = 0 WHERE alias = ?",
      alias,
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve();
      });
  });
}

export function deleteAliasEntry(alias: string) {
  return new Promise<void>(async (resolve, reject) => {
    const db = database('aliases');

    db.run(
      "DELETE FROM aliases WHERE alias = ?",
      alias,
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve();
      });
  });
}

export function createApiKeyEntry(address: string, label: string, token: string) {
  return new Promise<number>(async (resolve, reject) => {
    const db = database('credentials');

    db.run(
      "INSERT INTO api_keys (address, label, token) VALUES (?1, ?2, ?3)",
      {
        1: address,
        2: label,
        3: token,
      },
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve(this.lastID);
      });
  });
}

export function getUserApiKeys(address: string) {
  return new Promise<ApiKeyEntry[]>(async (resolve, reject) => {
    const db = database('credentials');

    db.all(
      "SELECT id, address, label, token FROM api_keys WHERE address = ?",
      address,
      function (err: any, res: any) {
        db.close();
        if (err) return reject(err);
        resolve(res);
      });
  });
}

export function getApiKeyByToken(token: string) {
  return new Promise<ApiKeyEntry | undefined>(async (resolve, reject) => {
    const db = database('credentials');

    db.get(
      "SELECT id, address, label, token FROM api_keys WHERE token = ?",
      token,
      function (err: any, res: any) {
        db.close();
        if (err) return reject(err);
        resolve(res);
      });
  });
}

export function getApiKeyById(id: number) {
  return new Promise<ApiKeyEntry | undefined>(async (resolve, reject) => {
    const db = database('credentials');

    db.get(
      "SELECT id, address, label, token FROM api_keys WHERE id = ?",
      id,
      function (err: any, res: any) {
        db.close();
        if (err) return reject(err);
        resolve(res);
      });
  });
}

export function deleteApiKey(id: number) {
  return new Promise<void>(async (resolve, reject) => {
    const db = database('credentials');

    db.run(
      "DELETE FROM api_keys WHERE id = ?",
      id,
      function (err: any) {
        db.close();
        if (err) return reject(err);
        resolve();
      });
  });
}
