import { EventTemplate, UnsignedEvent } from 'nostr-tools'
import { WindowNostr } from 'nostr-tools/nip07'
import { EventEmitter } from 'tseep'
import store from '../store/store'
import { SignedEvent } from '../types'
import { LoginMethodContext } from '../services/LoginMethodStrategy/loginMethodContext'

export class NostrController extends EventEmitter {
  private static instance: NostrController

  private constructor() {
    super()
  }
  private getNostrObject = () => {
    if (window.nostr) return window.nostr as WindowNostr

    throw new Error(
      `window.nostr object not present. Make sure you have an nostr extension installed/working properly.`
    )
  }

  public static getInstance(): NostrController {
    if (!NostrController.instance) {
      NostrController.instance = new NostrController()
    }
    return NostrController.instance
  }

  /**
   * Encrypts the given content for the specified receiver using NIP-44 encryption.
   *
   * @param receiver The public key of the receiver.
   * @param content The content to be encrypted.
   * @returns The encrypted content as a string.
   * @throws Error if the nostr extension does not support NIP-44, if the private key pair is not found, or if the login method is unsupported.
   */
  nip44Encrypt = async (receiver: string, content: string) => {
    // Retrieve the current login method from the application's redux state.
    const loginMethod = store.getState().auth.loginMethod
    const context = new LoginMethodContext(loginMethod)

    // Handle encryption when the login method is via an extension.
    return await context.nip44Encrypt(receiver, content)
  }

  /**
   * Decrypts the given content from the specified sender using NIP-44 decryption.
   *
   * @param sender The public key of the sender.
   * @param content The encrypted content to be decrypted.
   * @returns The decrypted content as a string.
   * @throws Error if the nostr extension does not support NIP-44, if the private key pair is not found, or if the login method is unsupported.
   */
  nip44Decrypt = async (sender: string, content: string) => {
    // Retrieve the current login method from the application's redux state.
    const loginMethod = store.getState().auth.loginMethod
    const context = new LoginMethodContext(loginMethod)

    // Handle decryption
    return await context.nip44Decrypt(sender, content)
  }

  /**
   * Signs an event with private key (if it is present in local storage) or
   * with browser extension (if it is present)
   * @param event - unsigned nostr event.
   * @returns - a promised that is resolved with signed nostr event.
   */
  signEvent = async (
    event: UnsignedEvent | EventTemplate
  ): Promise<SignedEvent> => {
    const loginMethod = store.getState().auth.loginMethod
    const context = new LoginMethodContext(loginMethod)

    return await context.signEvent(event)
  }

  nip04Encrypt = async (receiver: string, content: string): Promise<string> => {
    const loginMethod = store.getState().auth.loginMethod
    const context = new LoginMethodContext(loginMethod)

    return await context.nip04Encrypt(receiver, content)
  }

  /**
   * Decrypts a given content based on the current login method.
   *
   * @param sender - The sender's public key.
   * @param content - The encrypted content to decrypt.
   * @returns A promise that resolves to the decrypted content.
   */
  nip04Decrypt = async (sender: string, content: string): Promise<string> => {
    const loginMethod = store.getState().auth.loginMethod
    const context = new LoginMethodContext(loginMethod)

    return await context.nip04Decrypt(sender, content)
  }

  /**
   * Function will capture the public key from the nostr extension or if no extension present
   * function wil capture the public key from the local storage
   */
  capturePublicKey = async (): Promise<string> => {
    const nostr = this.getNostrObject()
    const pubKey = await nostr.getPublicKey().catch((err: unknown) => {
      if (err instanceof Error) {
        return Promise.reject(err.message)
      } else {
        return Promise.reject(JSON.stringify(err))
      }
    })

    if (!pubKey) {
      return Promise.reject('Error getting public key, user canceled')
    }

    return Promise.resolve(pubKey)
  }
}
