import * as nostrTools from '@nostr/tools';
import * as nip06 from '@nostr/tools/nip06';
import type { Database } from 'jsr:@db/sqlite';
import { decodeBase64, encodeBase64 } from 'jsr:@std/encoding@0.224/base64';
import { exists } from 'jsr:@std/fs';
import {
  decryptUint8Array,
  encryptUint8Array,
  encryptionKey,
} from './utils/encryption.ts';
import { getEveFilePath } from './utils/files.ts';
import { sql } from './utils/queries.ts';

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,
  );
}

/**
 * Get all CCNs from the database
 */
export function getAllCCNs(db: Database): { pubkey: string; name: string }[] {
  return sql`SELECT pubkey, name FROM ccns`(db) as {
    pubkey: string;
    name: string;
  }[];
}

/**
 * Create a new CCN and store it in the database
 *
 * @param db - The database instance
 * @param name - The name of the CCN
 * @param seed - The seed words for the CCN
 * @returns The public key and private key of the CCN
 */
export async function createNewCCN(
  db: Database,
  name: string,
  creator: string,
  seed?: string,
): Promise<{ pubkey: string; privkey: Uint8Array }> {
  const ccnSeed = seed || nip06.generateSeedWords();
  const ccnPrivateKey = nip06.privateKeyFromSeedWords(ccnSeed);
  const ccnPublicKey = nostrTools.getPublicKey(ccnPrivateKey);

  const ccnSeedPath = await getEveFilePath(`ccn_seeds/${ccnPublicKey}`);
  const ccnPrivPath = await getEveFilePath(`ccn_keys/${ccnPublicKey}`);

  await Deno.mkdir(await getEveFilePath('ccn_seeds'), { recursive: true });
  await Deno.mkdir(await getEveFilePath('ccn_keys'), { recursive: true });

  const encryptedPrivateKey = encryptUint8Array(ccnPrivateKey, encryptionKey);

  Deno.writeTextFileSync(ccnSeedPath, ccnSeed);
  Deno.writeTextFileSync(ccnPrivPath, encodeBase64(encryptedPrivateKey));

  db.run('BEGIN TRANSACTION');

  sql`INSERT INTO ccns (pubkey, name) VALUES (${ccnPublicKey}, ${name})`(db);
  sql`INSERT INTO allowed_writes (ccn_pubkey, pubkey) VALUES (${ccnPublicKey}, ${creator})`(
    db,
  );

  db.run('COMMIT TRANSACTION');

  return {
    pubkey: ccnPublicKey,
    privkey: ccnPrivateKey,
  };
}

/**
 * Get the private key for a specific CCN
 */
export async function getCCNPrivateKeyByPubkey(
  pubkey: string,
): Promise<Uint8Array> {
  const ccnPrivPath = await getEveFilePath(`ccn_keys/${pubkey}`);

  if (await exists(ccnPrivPath)) {
    const encryptedPrivateKey = Deno.readTextFileSync(ccnPrivPath);
    return decryptUint8Array(decodeBase64(encryptedPrivateKey), encryptionKey);
  }

  throw new Error(`CCN private key for ${pubkey} not found`);
}

export function isReplaceableEvent(kind: number): boolean {
  return (kind >= 10000 && kind < 20000) || kind === 0 || kind === 3;
}

export function isAddressableEvent(kind: number): boolean {
  return kind >= 30000 && kind < 40000;
}

export function isRegularEvent(kind: number): boolean {
  return (
    (kind >= 1000 && kind < 10000) ||
    (kind >= 4 && kind < 45) ||
    kind === 1 ||
    kind === 2
  );
}

export function isCCNReplaceableEvent(kind: number): boolean {
  return kind >= 60000 && kind < 65536;
}

export function parseATagQuery(aTagValue: string): {
  kind: number;
  pubkey: string;
  dTag?: string;
} {
  const parts = aTagValue.split(':');
  if (parts.length < 2) return { kind: 0, pubkey: '' };

  return {
    kind: Number.parseInt(parts[0], 10),
    pubkey: parts[1],
    dTag: parts.length > 2 ? parts[2] : undefined,
  };
}

/**
 * Get the single active CCN from the database
 * @returns The active CCN or null if none is active
 */
export function getActiveCCN(
  db: Database,
): { pubkey: string; name: string } | null {
  const result = sql`SELECT pubkey, name FROM ccns WHERE is_active = 1 LIMIT 1`(
    db,
  );
  return result.length > 0
    ? (result[0] as { pubkey: string; name: string })
    : null;
}