"use server";

import crypto from "crypto";
import fs from "fs/promises";
import { getServerSession } from "next-auth";
import { AuditLog, auditLog } from "./audit";
import { AliasEntry, AliasRequestEntry, ApiKeyEntry, approveAliasEntry, createAliasEntry, createApiKeyEntry, createTempAliasRequestEntry, createUserEntry, database, deleteAliasEntry, deleteApiKey, deleteTempAliasRequestEntry, getAlias, getAllAliases, getApiKeyById, getTempAliasRequestEntry, getUserAliases, getUserApiKeys, isAliasAvailable, setUserPassword } from "./db";
import { aliasesNeedApproval, anonymizeApiKey, isAdmin } from "./util";
import { generateAliasEmail } from "./util-server";

export async function fetchAllUsers(): Promise<string[]> {
  return new Promise(async (resolve, reject) => {
    const session = await getServerSession();
    if (!isAdmin(session)) return reject("Unauthenticated");

    const db = database('credentials');
    db.all("SELECT key FROM passwords", (err, res: any) => {
      if (err) return reject(err);

      resolve(res.map((row: any) => row.key));
    });
  });
}

export async function changeOwnPassword(newPass: string) {
  if (newPass.length < 8) throw new Error("Invalid password");
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  await setUserPassword(session.user.email, newPass);
  auditLog("changeOwnPassword");
}

export async function fetchOwnAliases(tempAliases?: boolean) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  return await getUserAliases(session.user.email, tempAliases);
}

export async function fetchUserAliases(email: string) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  if (!isAdmin(session)) throw new Error("Unauthorized");

  return await getUserAliases(email);
}

export async function fetchAllAliases() {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  if (!isAdmin(session)) throw new Error("Unauthorized");

  return await getAllAliases();
}

export async function aliasAvailable(email: string, searchTempRequests: boolean = false) {
  const session = await getServerSession();
  if (!session?.user) throw new Error("Unauthenticated");

  return await isAliasAvailable(email, searchTempRequests);
}

export async function createAlias(user: string, alias: string): Promise<AliasEntry> {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  if (!isAdmin(session)) throw new Error("Unauthenticated");
  if (!await isAliasAvailable(alias)) throw new Error("Alias unavailable");

  const id = await createAliasEntry(user, alias.toLowerCase(), false);

  const res = {
    id: id,
    address: user,
    alias: alias,
    pending: false,
    temporary: false,
  };

  auditLog('createAlias', res);
  return res;
}

export async function createAliasSelf(alias: string): Promise<AliasEntry> {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  const pending = aliasesNeedApproval(session);
  if (!await isAliasAvailable(alias)) throw new Error("Alias unavailable");

  const id = await createAliasEntry(session.user.email, alias.toLowerCase(), pending);
  const res = {
    id: id,
    address: session.user.email,
    alias: alias,
    pending: pending,
    temporary: false,
  };

  auditLog('requestAlias', res);
  return res;
}

export async function requestTemporaryAlias(
  label: string,
  labelAtEnd: boolean,
  style: 'words' | 'random',
  oldToken?: string,
): Promise<AliasRequestEntry> {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  if (!label.length || label.length > 16) throw new Error("Malformed request");

  let email = await generateAliasEmail(label, style, labelAtEnd);

  const request: AliasRequestEntry = {
    key: crypto.randomBytes(12).toString('base64'),
    address: session.user.email,
    alias: email,
    expires: Math.floor(Date.now() / 1000) + (60 * 60 * 4), // In 4 hours
  };

  await createTempAliasRequestEntry(
    request.key,
    request.address,
    request.alias,
    request.expires,
  );

  if (oldToken) {
    await deleteTempAliasRequestEntry(oldToken);
  }

  return request;
}

export async function claimTemporaryAlias(key: string): Promise<AliasEntry> {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  const data = await getTempAliasRequestEntry(key);
  if (!data || data.address != session.user.email) throw new Error("Unknown alias key");

  await deleteTempAliasRequestEntry(key);

  if (data.expires < Math.floor(Date.now() / 1000)) throw new Error("Alias request expired");

  const id = await createAliasEntry(data.address, data.alias, false, true);
  const alias: AliasEntry = {
    id: id,
    address: data.address,
    alias: data.alias,
    pending: false,
    temporary: true,
  };

  auditLog('createTempAlias', alias);

  return alias;
}

export async function disposeTempAliasRequest(key: string) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  const data = await getTempAliasRequestEntry(key);
  if (!data) return;
  if (data.address != session.user.email) throw new Error("Unauthorized");

  await deleteTempAliasRequestEntry(key);
}

export async function deleteAlias(alias: string) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  if (!isAdmin(session) && (await getAlias(alias))?.address != session.user.email) {
    throw new Error("Unauthorized");
  }

  await deleteAliasEntry(alias);
  auditLog('deleteAlias', alias);
}

export async function approveAlias(alias: string) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  if (!isAdmin(session)) throw new Error("Unauthorized");

  await approveAliasEntry(alias);
  auditLog('approveAlias', alias);
}

export async function createUser(email: string, password: string) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  if (!isAdmin(session)) throw new Error("Unauthorized");

  if (!await isAliasAvailable(email.toLowerCase())) {
    throw new Error("Alias unavailable");
  }

  if (password.length < 8 || !email.includes("@")) {
    throw new Error("Validation failed");
  }

  await createUserEntry(email, password);
  auditLog('createUser', email);
}

export async function fetchAuditLog(page: number): Promise<{ page: number, perPage: number, totalItems: number, items: (AuditLog & { index: number })[] }> {
  const itemsPerPage = 10;

  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");
  if (!isAdmin(session)) throw new Error("Unauthorized");

  if (!process.env.AUDIT_FILE_PATH) return { page: page, perPage: itemsPerPage, totalItems: 0, items: [] };

  const lines = (await fs.readFile(process.env.AUDIT_FILE_PATH))
    .toString("utf8")
    .trim()
    .split("\n")
    .reverse();

  if (page > Math.floor(lines.length / itemsPerPage)) {
    page = Math.ceil(lines.length / itemsPerPage) - 1;
  } else if (page < 0) {
    page = 0;
  }

  return {
    page: page,
    perPage: itemsPerPage,
    totalItems: lines.length,
    items: lines
      .slice(itemsPerPage * page, itemsPerPage * (page + 1))
      .map((item, i) => ({ ...JSON.parse(item), index: page * itemsPerPage + i })),
  };
}

export async function createApiKey(label: string): Promise<ApiKeyEntry> {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  if (!label.length || label.length > 64) throw new Error("Malformed label");

  const token = crypto.randomBytes(48).toString("base64");
  const id = await createApiKeyEntry(session.user.email, label, token);

  const key: ApiKeyEntry = {
    id: id,
    address: session.user.email,
    label: label,
    token: token,
  };

  auditLog('createApiKey', anonymizeApiKey(key));

  return key;
}

export async function fetchOwnApiKeys(): Promise<ApiKeyEntry[]> {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  const keys = await getUserApiKeys(session.user.email);
  return keys.map(anonymizeApiKey);
}

export async function deleteOwnApikey(id: number) {
  const session = await getServerSession();
  if (!session?.user?.email) throw new Error("Unauthenticated");

  const key = await getApiKeyById(id);
  if (!key || key.address != session.user.email) throw new Error("Unauthorized");

  await deleteApiKey(id);

  auditLog('deleteApiKey', anonymizeApiKey(key));
}
