import { exists } from "jsr:@std/fs";
import * as nostrTools from "@nostr/tools";
import * as nip06 from "@nostr/tools/nip06";
import { decodeBase64, encodeBase64 } from "jsr:@std/encoding@0.224/base64";
import { getEveFilePath } from "./utils/files.ts";
import {
  decryptUint8Array,
  encryptionKey,
  encryptUint8Array,
} from "./utils/encryption.ts";
import { NSec } from "@nostr/tools/nip19";

export function isLocalhost(req: Request): boolean {
  const url = new URL(req.url);
  const hostname = url.hostname;
  return (
    hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost"
  );
}

export function isValidJSON(str: string) {
  try {
    JSON.parse(str);
  } catch {
    return false;
  }
  return true;
}

export function isArray<T>(obj: unknown): obj is T[] {
  return Array.isArray(obj);
}

export function randomTimeUpTo2DaysInThePast() {
  const now = Date.now();
  const twoDaysAgo = now - 2 * 24 * 60 * 60 * 1000 - 3600 * 1000; // 1 hour buffer in case of clock skew
  return Math.floor(
    (Math.floor(Math.random() * (now - twoDaysAgo)) + twoDaysAgo) / 1000,
  );
}

export async function getCCNPubkey(): Promise<string> {
  const ccnPubPath = await getEveFilePath("ccn.pub");
  const seedPath = await getEveFilePath("ccn.seed");
  const doWeHaveKey = await exists(ccnPubPath);
  if (doWeHaveKey) return Deno.readTextFileSync(ccnPubPath);
  const ccnSeed = Deno.env.get("CCN_SEED") ||
    ((await exists(seedPath))
      ? Deno.readTextFileSync(seedPath)
      : nip06.generateSeedWords());
  const ccnPrivateKey = nip06.privateKeyFromSeedWords(ccnSeed);
  const ccnPublicKey = nostrTools.getPublicKey(ccnPrivateKey);
  const encryptedPrivateKey = encryptUint8Array(ccnPrivateKey, encryptionKey);

  Deno.writeTextFileSync(ccnPubPath, ccnPublicKey);
  Deno.writeTextFileSync(
    await getEveFilePath("ccn.priv"),
    encodeBase64(encryptedPrivateKey),
  );
  Deno.writeTextFileSync(seedPath, ccnSeed);

  return ccnPublicKey;
}

export async function getMLSPrivateKey(): Promise<NSec> {
  const mlsPrivPath = await getEveFilePath("mls.priv");
  const doWeHaveKey = await exists(mlsPrivPath);
  if (doWeHaveKey) {
    const encryptedPrivateKey = Deno.readTextFileSync(mlsPrivPath);
    const decryptedPrivateKey = decryptUint8Array(
      decodeBase64(encryptedPrivateKey),
      encryptionKey,
    );
    return nostrTools.nip19.nsecEncode(decryptedPrivateKey);
  }
  const mlsPrivateKey = nostrTools.generateSecretKey();
  const encryptedPrivateKey = encryptUint8Array(mlsPrivateKey, encryptionKey);
  Deno.writeTextFileSync(mlsPrivPath, encodeBase64(encryptedPrivateKey));
  return nostrTools.nip19.nsecEncode(mlsPrivateKey);
}

export async function getCCNPrivateKey(): Promise<Uint8Array> {
  const encryptedPrivateKey = Deno.readTextFileSync(
    await getEveFilePath("ccn.priv"),
  );
  return decryptUint8Array(decodeBase64(encryptedPrivateKey), encryptionKey);
}

/**
 * Compares two byte-like objects in a constant-time manner to prevent timing attacks.
 *
 * @param a - First byte-like object to compare
 * @param b - Second byte-like object to compare
 * @returns boolean indicating whether the inputs contain identical bytes
 */
export function bytesEqual<
  T extends Uint8Array | number[] | string,
>(a: T, b: T): boolean {
  const aLength = a.length;
  const bLength = b.length;
  let result = aLength !== bLength ? 1 : 0;
  const maxLength = Math.max(aLength, bLength);
  for (let i = 0; i < maxLength; i++) {
    const aVal = i < aLength
      ? (typeof a === "string" ? a.charCodeAt(i) : a[i])
      : 0;
    const bVal = i < bLength
      ? (typeof b === "string" ? b.charCodeAt(i) : b[i])
      : 0;
    result |= aVal ^ bVal;
  }
  return result === 0;
}