import { promisify } from "util";
import { pbkdf2, randomBytes } from "crypto";
import dayjs from "dayjs";
import { PoolConnection } from "mariadb";
import { readFile } from "fs";
import { join } from "path";
import { getDatetime } from "taehui-ts/date";

import DB from "src/system/DB";
import Configure from "src/system/Configure";
import { equalCipher } from "src/Utility";
import { wwwAXIOS } from "src/Www";

const pw = promisify(pbkdf2);
const rf = promisify(readFile);

const putTotem = async (db: PoolConnection, avatarID: string) => {
  const data = await DB.getTotem(db, avatarID);

  const datetime = getDatetime();
  if (data && dayjs(data.date).add(1, "hour").isAfter(datetime)) {
    await DB.setTotemDateAsAvatarID(db, avatarID, datetime);
    return data.totem;
  }

  return DB.putTotem(db, avatarID, datetime);
};

export const getTotem = async (
  pendingAvatarID: string,
  pendingAvatarCipher: string,
) => {
  return DB.ta(async (db) => {
    const data = await DB.getAvatarAsAvatarID(db, pendingAvatarID);
    if (data) {
      const { avatarID, avatarCipher, avatarName, level, fax, avatarIntro } =
        data;
      const isOK = await equalCipher(avatarCipher, pendingAvatarCipher);
      if (isOK) {
        await DB.setLastDate(db, avatarID, getDatetime());
        return {
          totem: await putTotem(db, avatarID),
          avatarID,
          avatarName,
          level,
          fax,
          avatarIntro,
        };
      } else {
        return null;
      }
    }
  });
};

const getDBCipher = async (avatarCipher: string) => {
  const salt = randomBytes(24).toString("base64");

  return `sha256:12000:${salt}:${Buffer.from(
    await pw(avatarCipher, salt, 12000, 24, "sha256"),
  ).toString("base64")}`;
};

export const doModifyAvatar = async (
  avatarID: string,
  avatarCipher: string,
  avatarCipherModified: string,
  avatarName: string,
  fax: string,
  avatarIntro: string,
) => {
  if (avatarCipher && avatarName && (!fax || /^.+@.+$/.test(fax))) {
    return DB.ta(async (db) => {
      if (
        await DB.doModifyAvatar(
          db,
          avatarID,
          avatarCipher,
          avatarCipherModified && (await getDBCipher(avatarCipherModified)),
          avatarName,
          fax,
          avatarIntro,
        )
      ) {
        if (avatarCipherModified) {
          await DB.wipeTotem(db, avatarID);
          await wwwAXIOS.patch(`${Configure.www.qwilight}/totem`, avatarID);
        }
        return true;
      } else {
        return false;
      }
    });
  } else {
    return false;
  }
};

export const doModifyAvatarIntro = async (
  avatarID: string,
  avatarIntro: string,
) => {
  return DB.doModifyAvatarIntro(avatarID, avatarIntro);
};

export const postAvatar = async (
  avatarID: string,
  avatarCipher: string,
  avatarName: string,
  avatarIP: string,
  fax: string,
) => {
  return (
    avatarID &&
    !avatarID.startsWith("@") &&
    !avatarID.startsWith("#") &&
    !avatarID.startsWith("$") &&
    !avatarID.startsWith("*") &&
    avatarCipher &&
    avatarName &&
    (!fax || /^.+@.+$/.test(fax)) &&
    DB.ta(async (db) => {
      if (await DB.getAvatarAsAvatarID(db, avatarID)) {
        return false;
      } else {
        return DB.postAvatar(
          avatarID,
          await getDBCipher(avatarCipher),
          avatarName,
          avatarIP,
          fax,
          getDatetime(),
        );
      }
    })
  );
};

export const quitTotem = async (avatarID: string) => {
  return DB.ta(async (db) =>
    Promise.all([
      DB.setLastDate(db, avatarID, getDatetime()),
      DB.wipeTotem(db, avatarID),
    ]),
  );
};

export const getAvatarDrawing = async (avatarID: string) => {
  try {
    return await rf(join(Configure.path.avatar, avatarID + ".png"));
  } catch (e: any) {
    if (e.code === "ENOENT") {
      return null;
    }

    throw e;
  }
};

export const getLatestAvatars = async () => {
  return DB.getLatestAvatars();
};

export const doInvalidateDrawing = async (avatarID: string) => {
  return wwwAXIOS.patch(`${Configure.www.qwilight}/drawing`, avatarID);
};
