import base45 from 'base45'
import { KJUR } from 'jsrsasign'

import { parseTicketId } from '../blockchain/utils'
import { sign, verify } from '../utils/certificate'
import { createInvalidateCall, createInvalidatePayload, TickedIds } from '..'

/**
 * Encodes the payload and the signature into the expected QR code payload
 *
 * @param payload JSON stringified payload
 * @param signatureHex Hex encoded signature of the payload
 * @returns base 45 encoded string
 */
export function encodeQrPayload(payload: string, signatureHex: string) {
	const payloadBuffer = Buffer.from(payload)
	const signatureBuffer = Buffer.from(signatureHex, 'hex')
	const payloadEncoded = base45.encode(payloadBuffer)
	const signatureEncoded = base45.encode(signatureBuffer)
	return `${payloadEncoded}:::${signatureEncoded}`
}

/**
 * Decode a base 45 encoded QR code payload
 *
 * @param encodedPayload what you scanned from the QR code
 * @returns payload and its signature
 */
export function decodeQrPayload(encodedPayload: string) {
	const [payloadEncoded, signatureEncoded] = encodedPayload.split(':::')
	const payloadBuffer = base45.decode(payloadEncoded)
	const signatureBuffer = base45.decode(signatureEncoded)
	const payload = payloadBuffer.toString()
	const signature = signatureBuffer.toString('hex')
	return {
		payload,
		signature,
	}
}

type TicketQrContent = {
	id: string
}

/**
 * Signs and encodes ticket data into the string which will be put in the QR code
 *
 * @param eventId event to which the ticket belongs
 * @param ticketConfigId ticket config of the ticket
 * @param sequenceNumber the serial number of the ticket
 * @param privateKey the private key which is used to sign the ticket
 * @returns string to display as a tickets QR code
 */
export function createQrPayloadForTicket(
	eventId: number,
	ticketConfigId: number,
	sequenceNumber: number,
	privateKey: string | KJUR.crypto.ECDSA
): string {
	// Encode the ticket ID
	const ticketId = `E${eventId}TC${ticketConfigId}T${sequenceNumber}`
	const payload = JSON.stringify({
		id: ticketId,
	})
	const signature = sign(payload, privateKey)
	return encodeQrPayload(payload, signature)
}

/**
 * Decodes, verifies the signature of the QR code payload and constructs the blockchain
 * function call to invalidate the ticket
 *
 * @param qrCodePayload content of the QR code
 * @param invalidationDate the time at which the ticket was scanned
 * @param publicKey key used to verify the ticket signature, if specified
 * @returns blockchain function call for ticket invalidation
 * @throws Error if the signature does not match the payload
 */
export function qrToTicketInvalidateCall(
	qrCodePayload: string,
	invalidationDate: Date,
	publicKey?: string | KJUR.crypto.ECDSA
) {
	const qrContent = decodeQrPayload(qrCodePayload)
	if (publicKey) {
		const signatureValid = verify(
			qrContent.payload,
			qrContent.signature,
			publicKey
		)
		if (!signatureValid) {
			throw new Error('Invalid ticket signature')
		}
	}
	const payload = JSON.parse(qrContent.payload) as TicketQrContent

	const args = createInvalidatePayload(payload.id, invalidationDate)
	return createInvalidateCall(args)
}

/**
 * Decodes and verifies the signature of the QR code payload and returns the IDs
 *
 * @param qrCodePayload content of the QR code
 * @param publicKey key used to verify the ticket signature, if specified
 * @returns ticket IDs
 * @throws Error if the signature does not match the payload
 */
export function qrToTicketData(
	qrCodePayload: string,
	publicKey?: string | KJUR.crypto.ECDSA
): TickedIds {
	const qrContent = decodeQrPayload(qrCodePayload)
	if (publicKey) {
		const signatureValid = verify(
			qrContent.payload,
			qrContent.signature,
			publicKey
		)
		if (!signatureValid) {
			throw new Error('Invalid ticket signature')
		}
	}
	const payload = JSON.parse(qrContent.payload) as TicketQrContent

	return parseTicketId(payload.id)
}
