import {
  finalizeEvent,
  verifyEvent,
  getPublicKey,
  nip19,
  getEventHash,
} from "nostr-tools";
import { useWebSocketImplementation, Relay } from "nostr-tools/relay";
import WebSocket from "ws";
useWebSocketImplementation(WebSocket);

const TWO_DAYS = 48 * 60 * 60 * 1000;
const RELAY = "TODO";
const BOT = JSON.stringify({
  name: "BOT",
  about: "a little exercise",
});
let intervalId;

try {
  const args = [...process.argv];
  args.shift();
  args.shift();

  if (!args.includes("-nsec")) {
    console.error("BOT NEEDS NSEC.\n-nsec <nsec>");
    process.exit(1);
  }

  const index = args.findIndex((arg) => arg === "-nsec");
  const nsec = args[index + 1];

  if (typeof nsec === "undefined" || nsec === "" || !nsec.startsWith("nsec")) {
    console.error("BOT NSEC MISSING");
    process.exit(1);
  }

  const sk = nip19.decode(nsec).data;
  if (!sk) {
    console.error("BOT NSEC BAD.");
    process.exit(1);
  }

  const pk = getPublicKey(sk);
  const profileEvent = finalizeEventWithPoW(
    {
      kind: 0,
      content: BOT,
      created_at: Math.floor(Date.now() / 1000),
      tags: [],
    },
    sk
  );
  const isGood = verifyEvent(profileEvent);
  if (isGood) {
    (async () => {
      try {
        const relay = await Relay.connect(RELAY);
        await relay.publish(profileEvent);
        console.log("IT IS ALIVE!!");
        relay.close();
      } catch (error) {
        exit(error);
      }
    })();
  }

  const run = () => {
    const today = new Date();
    const dayOfWeek = today.getUTCDay();
    const message = `${dayOfWeek % 6 === 0 ? "gfy" : "GM"} fiatjaf`;

    const event = finalizeEventWithPoW(
      {
        kind: 1,
        content: message,
        created_at: Math.floor(Date.now() / 1000),
        tags: [],
      },
      sk
    );

    const isGood = verifyEvent(event);
    if (isGood) {
      (async () => {
        try {
          console.log("BOT IS CONNECTING..");
          const relay = await Relay.connect(RELAY);
          console.log("BOT CONNECTED");
          await relay.publish(event);
          console.log(`[${today.toISOString()}] ${message}`, event);
          relay.close();
        } catch (error) {
          exit(error);
        }
      })();
    }
  };

  console.log("BOT IS BOTTING as", nip19.npubEncode(pk));
  intervalId = setInterval(run, TWO_DAYS);
} catch (error) {
  exit(error);
}

function exit(error) {
  console.error(`[${new Date().toISOString()}]`, "RIP BOT", error);
  clearInterval(intervalId);
  process.exit(1);
}

function finalizeEventWithPoW(event, sk, difficulty = 0) {
  if (!difficulty) return finalizeEvent(event, sk);

  const pk = getPublicKey(sk);
  let nonce = 0;
  let leadingZeroes = 0;
  let unsignedEvent = { ...event, pubkey: pk };
  while (true) {
    unsignedEvent.tags = [["nonce", nonce.toString(), difficulty.toString()]];
    unsignedEvent.created_at = Math.floor(Date.now() / 1000);

    const hash = getEventHash(unsignedEvent);
    leadingZeroes = countLeadingZeroes(hash);

    if (leadingZeroes >= difficulty) {
      return finalizeEvent(unsignedEvent, sk);
    }

    nonce++;
  }
}

// hex should be a hexadecimal string (with no 0x prefix)
function countLeadingZeroes(hex) {
  let count = 0;

  for (let i = 0; i < hex.length; i++) {
    const nibble = parseInt(hex[i], 16);
    if (nibble === 0) {
      count += 4;
    } else {
      count += Math.clz32(nibble) - 28;
      break;
    }
  }

  return count;
}