import { EventTemplate, UnsignedEvent } from 'nostr-tools'
import { EventEmitter } from 'tseep'
import store from '../store/store'
import { SignedEvent } from '../types'
import { LoginMethodContext } from '../services/LoginMethodStrategy/loginMethodContext'
import { clear, unixNow } from '../utils'
import { LoginMethod } from '../store/auth/types'
import { logout as nostrLogout } from 'nostr-login'
import { userLogOutAction } from '../store/actions'

export class NostrController extends EventEmitter {
  private static instance: NostrController

  private constructor() {
    super()
  }

  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)

    const authkey = store.getState().auth.usersPubkey
    const signedEvent = await context.signEvent(event)
    const pubkey = signedEvent.pubkey

    // Forcefully log out the user if we detect missmatch between pubkeys
    // Allow undefined authkey, intial log in
    if (authkey && authkey !== pubkey) {
      if (loginMethod === LoginMethod.nostrLogin) {
        nostrLogout()
      }
      store.dispatch(userLogOutAction())
      clear()
      throw new Error('User missmatch.\n\nPlease log in again.')
    }

    return signedEvent
  }

  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 signedEvent
   */
  capturePublicKey = async (): Promise<string> => {
    try {
      const timestamp = unixNow()
      const { href } = window.location

      const authEvent: EventTemplate = {
        kind: 27235,
        tags: [
          ['u', href],
          ['method', 'GET']
        ],
        content: '',
        created_at: timestamp
      }

      const signedAuthEvent = await this.signEvent(authEvent)
      const pubkey = signedAuthEvent.pubkey

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

      return Promise.resolve(pubkey)
    } catch (error) {
      if (error instanceof Error) {
        return Promise.reject(error.message)
      } else {
        return Promise.reject(JSON.stringify(error))
      }
    }
  }
}
