import { NDKUser } from '@nostr-dev-kit/ndk';
import { LRUCache } from 'lru-cache';
import { NostrEvent } from 'nostr-fetch';
import { nip19 } from 'nostr-tools';

import { useNDK } from '@libs/ndk/provider';
import {
  countTotalNotes,
  createChat,
  createNote,
  getActiveAccount,
  getLastLogin,
  updateAccount,
} from '@libs/storage';

import { nHoursAgo } from '@utils/date';

export function useNostr() {
  const { ndk, relayUrls, fetcher } = useNDK();

  async function fetchNetwork() {
    const account = await getActiveAccount();
    const follows = new Set<string>();
    const lruNetwork = new LRUCache<string, string, void>({ max: 300 });

    let network: string[];

    // fetch user's follows
    const user = ndk.getUser({ hexpubkey: account.pubkey });
    const list = await user.follows();
    list.forEach((item: NDKUser) => {
      follows.add(nip19.decode(item.npub).data as string);
    });

    // fetch network
    if (!account.network) {
      const events = await fetcher.fetchAllEvents(
        relayUrls,
        { kinds: [3], authors: [...follows] },
        { since: 0 },
        { skipVerification: true }
      );

      events.forEach((event: NostrEvent) => {
        event.tags.forEach((tag) => {
          if (tag[0] === 'p') lruNetwork.set(tag[1], tag[1]);
        });
      });

      network = [...network.values()] as string[];
    } else {
      network = account.network;
    }

    // update user in db
    await updateAccount('follows', [...follows]);
    await updateAccount('network', network);

    return [...new Set([...follows, ...network])];
  }

  const fetchNotes = async () => {
    try {
      const network = (await fetchNetwork()) as string[];

      const totalNotes = await countTotalNotes();
      const lastLogin = await getLastLogin();

      if (network.length > 0) {
        let since: number;
        if (totalNotes === 0 || lastLogin === 0) {
          since = nHoursAgo(6);
        } else {
          since = lastLogin;
        }

        const events = await fetcher.fetchAllEvents(
          relayUrls,
          { kinds: [1], authors: network },
          { since: since },
          { skipVerification: true }
        );

        for (const event of events) {
          await createNote(
            event.id,
            event.pubkey,
            event.kind,
            event.tags,
            event.content,
            event.created_at
          );
        }
      }

      return true;
    } catch (e) {
      console.log('error: ', e);
    }
  };

  const fetchChats = async () => {
    try {
      const account = await getActiveAccount();
      const lastLogin = await getLastLogin();
      const incomingMessages = await fetcher.fetchAllEvents(
        relayUrls,
        {
          kinds: [4],
          '#p': [account.pubkey],
        },
        { since: lastLogin },
        { skipVerification: true }
      );

      for (const event of incomingMessages) {
        const receiverPubkey = event.tags.find((t) => t[0] === 'p')[1] || account.pubkey;
        await createChat(
          event.id,
          receiverPubkey,
          event.pubkey,
          event.content,
          event.tags,
          event.created_at
        );
      }

      return true;
    } catch (e) {
      console.log('error: ', e);
    }
  };

  return { fetchNotes, fetchChats };
}
