import { AxiosInstance } from 'axios'

import {
	Digest,
	KeyPair,
	TxStatus,
	SignedDigest,
	TransactionProposalPayload,
	BlockchainFunctionParams,
} from './types'
import { signDigest } from './signer'
import { HealthStatus } from '../common/types'
import { toSnakeCase } from '../common/converter'

/**
 * Service class for blockchain transactions.
 */
export class BlockchainService {
	constructor(readonly client: AxiosInstance, readonly version: string) {}

	/**
	 * Returns true if the service is reachable
	 *
	 * @returns Services' online status
	 */
	async health(): Promise<HealthStatus> {
		try {
			const res = await this.client.get(`event/health`)
			if (res.data.status === 'ok') {
				return { online: true }
			}
		} catch (e) {
			// Do nothing
		}

		return { online: false }
	}

	/**
	 * This is the first step in committing data to the blockchain.
	 * Creates a new proposal which need to be signed with the private key.
	 *
	 * @param proposal New proposal data
	 * @returns Digest hash of the proposal
	 */
	async createProposal(proposal: TransactionProposalPayload): Promise<Digest> {
		// The payload is snake cased because chaincode consumes it
		const res = await this.client.post(
			`blockchain/${this.version}/transaction/proposal`,
			{
				...proposal,
				args: [JSON.stringify(toSnakeCase(proposal.args))],
			}
		)

		return res.data.data
	}

	/**
	 * This is the second step in committing data to the blockchain.
	 * Submits the proposal signature so that it can be endorsed.
	 * It is signed with the user's private key.
	 *
	 * @param proposal Proposal's digest and signature
	 * @returns Transaction (tx) digest, used in the commit transaction step
	 */
	async createTransaction(proposal: SignedDigest): Promise<Digest> {
		const res = await this.client.post(
			`blockchain/${this.version}/transaction/proposal/${proposal.digest}`,
			{ signature: proposal.signature }
		)

		return res.data.data
	}

	/**
	 * Third and final step in creating a transaction.
	 * Commits a transaction. Once a proposal is submitted,
	 * signed and endorsed it can be committed as a transaction to the blockchain.
	 *
	 * @param tx Signed transaction
	 * @returns Status of the transaction
	 */
	async commitTransaction(tx: SignedDigest): Promise<TxStatus> {
		const res = await this.client.post(
			`blockchain/${this.version}/transaction/${tx.digest}`,
			{ signature: tx.signature }
		)

		return res.data.data
	}

	/**
	 * Creates a proposal, signs it and commits it to the blockchain.
	 *
	 * @param proposal Transaction proposal containing the bc function and arguments
	 * @param keys Private key and certificate to sign the intermediate steps
	 * @returns Transaction commit status
	 */
	async sendTransaction(
		proposal: BlockchainFunctionParams,
		keys: KeyPair
	): Promise<TxStatus> {
		const proposalDigest = await this.createProposal({
			fcn: proposal.fcn,
			certificate: keys.certificate,
			args: proposal.args,
		})
		const proposalSignature = signDigest(keys.privateKey, proposalDigest.digest)

		const txDigest = await this.createTransaction({
			digest: proposalDigest.digest,
			signature: proposalSignature,
		})
		const txSignature = signDigest(keys.privateKey, txDigest.digest)

		return this.commitTransaction({
			digest: txDigest.digest,
			signature: txSignature,
		})
	}

	/**
	 * Sends online transaction to the blockchain service.
	 *
	 * @param proposal Transaction proposal containing the bc function and arguments
	 * @param keys Private key and certificate for which the transaction should be invoked.
	 * @returns Response from the chaincode.
	 */
	async sendOnlineTransaction(
		proposal: BlockchainFunctionParams,
		keys: KeyPair
	): Promise<any> {
		const body = {
			...proposal,
			...keys,
			args: [JSON.stringify(toSnakeCase(proposal.args))],
		}
		const res = await this.client.post(
			`blockchain/${this.version}/transaction`,
			body
		)

		return res.data.data
	}
}
