import NDK, {
  NDKEvent,
  NDKUser,
  NDKSigner,
  zapInvoiceFromEvent,
  type NostrEvent,
} from "@nostr-dev-kit/ndk";
import { requestProvider } from "webln";
import { bech32 } from "@scure/base";
import { z } from "zod";
import { createZodFetcher } from "zod-fetch";
import { getTagValues, getTagsAllValues } from "../nostr/utils";
import { unixTimeNowInSeconds } from "../nostr/dates";
import { createEvent } from "./create";
import { findEphemeralSigner } from "@/lib/actions/ephemeral";
import { log } from "@/lib/utils";

const fetchWithZod = createZodFetcher();
const ZapEndpointResponseSchema = z.object({
  nostrPubkey: z.string(),
});

export async function sendZap(
  ndk: NDK,
  amount: number,
  _event: NostrEvent,
  comment?: string,
) {
  log("func", "sendZap");
  const event = await new NDKEvent(ndk, _event);
  log("info", JSON.stringify(event));
  const pr = await event.zap(amount * 1000, comment);
  if (!pr) {
    log("info", "No PR");
    return;
  }
  const webln = await requestProvider();
  return await webln.sendPayment(pr);
}
export async function checkPayment(
  ndk: NDK,
  tagId: string,
  pubkey: string,
  event: NostrEvent,
) {
  const paymentEvents = await ndk.fetchEvents({
    kinds: [9735],
    ["#a"]: [tagId],
  });
  if (!paymentEvents) return;
  const paymentEvent = Array.from(paymentEvents).find(
    (e) => zapInvoiceFromEvent(e)?.zappee === pubkey,
  );
  if (!paymentEvent) return;
  const invoice = zapInvoiceFromEvent(paymentEvent);
  if (!invoice) {
    console.log("No invoice");
    return;
  }

  const zappedUser = ndk.getUser({
    hexpubkey: invoice.zapped,
  });
  await zappedUser.fetchProfile();
  if (!zappedUser.profile) {
    console.log("No zappedUser profile");
    return;
  }
  const { lud16, lud06 } = zappedUser.profile;
  let zapEndpoint: null | string = null;

  if (lud16 && !lud16.startsWith("LNURL")) {
    const [name, domain] = lud16.split("@");
    zapEndpoint = `https://${domain}/.well-known/lnurlp/${name}`;
  } else if (lud06) {
    const { words } = bech32.decode(lud06, 1e3);
    const data = bech32.fromWords(words);
    const utf8Decoder = new TextDecoder("utf-8");
    zapEndpoint = utf8Decoder.decode(data);
  }
  if (!zapEndpoint) {
    console.log("No zapEndpoint");
    return;
  }

  const { nostrPubkey } = await fetchWithZod(
    // The schema you want to validate with
    ZapEndpointResponseSchema,
    // Any parameters you would usually pass to fetch
    zapEndpoint,
    {
      method: "GET",
    },
  );
  if (!nostrPubkey) return;
  console.log("nostrPubkey", nostrPubkey);
  console.log("Invoice amount", invoice.amount);
  console.log("Price", parseInt(getTagValues("price", event.tags) ?? "0"));
  return (
    nostrPubkey === paymentEvent.pubkey &&
    invoice.amount >= parseInt(getTagValues("price", event.tags) ?? "0")
  );
}
export async function updateListUsersFromZaps(
  ndk: NDK,
  tagId: string,
  event: NostrEvent,
) {
  log("func", "updateListUsersFromZaps");
  const SECONDS_IN_MONTH = 2_628_000;
  const SECONDS_IN_YEAR = SECONDS_IN_MONTH * 365;
  const paymentEvents = await ndk.fetchEvents({
    kinds: [9735],
    ["#a"]: [tagId],
    since: unixTimeNowInSeconds() - SECONDS_IN_YEAR,
  });
  const paymentInvoices = Array.from(paymentEvents).map((paymentEvent) =>
    zapInvoiceFromEvent(paymentEvent),
  );

  const currentUsers = getTagsAllValues("p", event.tags);
  let validUsers: string[][] = currentUsers.filter(
    ([pubkey, relay, petname, expiryUnix]) =>
      parseInt(expiryUnix ?? "0") > unixTimeNowInSeconds(),
  );
  const newUsers: string[] = currentUsers.map(([pub]) => pub as string);

  for (const paymentInvoice of paymentInvoices) {
    if (
      !paymentInvoice ||
      validUsers.find(([pubkey]) => pubkey === paymentInvoice.zappee)
    ) {
      continue;
    }
    const isValid = await checkPayment(
      ndk,
      tagId,
      paymentInvoice.zappee,
      event,
    );
    if (isValid) {
      validUsers.push([
        paymentInvoice.zappee,
        "",
        "",
        (unixTimeNowInSeconds() + SECONDS_IN_YEAR).toString(),
      ]);
      newUsers.push(paymentInvoice.zappee);
      // Send old codes to user
    }
  }
  log("info", "New users", newUsers.toString());

  await sendCodesToNewUsers(ndk, newUsers, tagId, event);

  // Add self;
  const selfIndex = validUsers.findIndex(([vu]) => vu === event.pubkey);
  if (selfIndex === -1) {
    validUsers.push([event.pubkey, "", "self", "4000000000"]);
  }
  return createEvent(ndk, {
    ...event,
    kind: event.kind as number,
    tags: [
      ...event.tags.filter(([key]) => key !== "p"),
      ...validUsers.map((user) => ["p", ...user]),
    ],
  });
}

async function sendCodesToNewUsers(
  ndk: NDK,
  users: string[],
  tagId: string,
  event: NostrEvent,
) {
  const signer = await findEphemeralSigner(ndk, ndk.signer!, {
    associatedEventNip19: new NDKEvent(ndk, event).encode(),
  });
  log("func", "sendCodesToNewUsers");
  log("info", "Signer", signer?.toString());

  if (!signer) return;
  const delegate = await signer.user();
  const messages = await ndk.fetchEvents({
    authors: [delegate.pubkey],
    kinds: [4],
    ["#p"]: [tagId.split(":")?.[1] ?? ""],
  });
  const codes: [string, string][] = [];
  for (const message of Array.from(messages)) {
    await message.decrypt();
    codes.push([getTagValues("e", message.tags) ?? "", message.content]);
  }
  log("info", "codes", codes.toString());

  for (const user of users) {
    for (const [event, code] of codes) {
      const messageEvent = new NDKEvent(ndk, {
        content: code,
        kind: 4,
        tags: [
          ["p", user],
          ["e", event],
          ["client", "flockstr"],
        ],
        pubkey: delegate.pubkey,
      } as NostrEvent);
      log("info", "sending message");
      await messageEvent.encrypt(new NDKUser({ hexpubkey: user }), signer);
      await messageEvent.sign(signer);
      await messageEvent.publish();
    }
  }
}