import { OpenTimestamp, OpenTimestampUpgradeVerifyResponse } from '../types'
import { retry } from './retry.ts'
import { bytesToHex } from '@noble/hashes/utils'
import { utf8Encoder } from 'nostr-tools/utils'
import { hexStringToUint8Array } from './string.ts'

/**
 * Generates a timestamp for the provided nostr event ID.
 * @returns Timestamp with its value and the nostr event ID.
 */
export const generateTimestamp = async (
  nostrId: string
): Promise<OpenTimestamp | undefined> => {
  try {
    return {
      value: await retry(() => timestamp(nostrId)),
      nostrId: nostrId
    }
  } catch (error) {
    console.error(error)
    return
  }
}

/**
 * Attempts to upgrade (i.e. add Bitcoin blockchain attestations) and verify the provided timestamp.
 * Returns the same timestamp, alongside additional information required to decide if any further
 * timestamp updates are required.
 * @param timestamp
 */
export const upgradeAndVerifyTimestamp = async (
  timestamp: OpenTimestamp
): Promise<OpenTimestampUpgradeVerifyResponse> => {
  const upgradedResult = await upgrade(timestamp)
  return await verify(upgradedResult)
}

/**
 * Attempts to upgrade a timestamp. If an upgrade is available,
 * it will add new data to detachedTimestamp.
 * The upgraded flag indicates if an upgrade has been performed.
 * @param t - timestamp
 */
export const upgrade = async (
  t: OpenTimestamp
): Promise<OpenTimestampUpgradeVerifyResponse> => {
  const detachedTimestamp =
    window.OpenTimestamps.DetachedTimestampFile.deserialize(
      hexStringToUint8Array(t.value)
    )

  const changed: boolean =
    await window.OpenTimestamps.upgrade(detachedTimestamp)
  if (changed) {
    const updated = detachedTimestamp.serializeToBytes()

    const value = {
      ...t,
      timestamp: bytesToHex(updated)
    }

    return {
      timestamp: value,
      upgraded: true
    }
  }
  return {
    timestamp: t,
    upgraded: false
  }
}

/**
 * Attempts to verify a timestamp. If verification is available,
 * it will be included in the returned object.
 * @param t - timestamp
 */
export const verify = async (
  t: OpenTimestampUpgradeVerifyResponse
): Promise<OpenTimestampUpgradeVerifyResponse> => {
  const detachedNostrId = window.OpenTimestamps.DetachedTimestampFile.fromBytes(
    new window.OpenTimestamps.Ops.OpSHA256(),
    utf8Encoder.encode(t.timestamp.nostrId)
  )

  const detachedTimestamp =
    window.OpenTimestamps.DetachedTimestampFile.deserialize(
      hexStringToUint8Array(t.timestamp.value)
    )

  const res = await window.OpenTimestamps.verify(
    detachedTimestamp,
    detachedNostrId
  )

  return {
    ...t,
    verified: !!res.bitcoin,
    verification: res?.bitcoin || null
  }
}

/**
 * Timestamps a nostrId.
 * @param nostrId
 */
const timestamp = async (nostrId: string): Promise<string> => {
  const detachedTimestamp =
    window.OpenTimestamps.DetachedTimestampFile.fromBytes(
      new window.OpenTimestamps.Ops.OpSHA256(),
      utf8Encoder.encode(nostrId)
    )

  await window.OpenTimestamps.stamp(detachedTimestamp)
  const ctx = new window.OpenTimestamps.Context.StreamSerialization()
  detachedTimestamp.serialize(ctx)
  const timestampBytes = ctx.getOutput()
  return bytesToHex(timestampBytes)
}
