import { AxiosInstance } from 'axios'
import queryString from 'query-string'

import { HealthStatus } from '../common/types'
import {
	IPayout,
	IPayoutData,
	StripeConfig,
	CryptoPayment,
	PaymentAmount,
	PaymentProvider,
	CryptoPaymentMeta,
	CreatePaymentQuery,
	PaymentIntentResponse,
	OpenPaymentResponse,
	OpenPaymentMeta,
	OpenPaymentOrderQuery,
	OpenPaymentOrderData,
} from './types'

/**
 * Service class for payment API calls. Requires an organizer to be set (call to `useOrganizer`)
 */
export class PaymentService {
	constructor(readonly client: AxiosInstance, readonly version: string) {}

	/**
	 * Returns true if the service is reachable
	 * Currently the
	 *
	 * @returns Services' online status
	 */
	async health(): Promise<HealthStatus> {
		try {
			const res = await this.client.get(`payment/health`)
			// ATM there is an issue with the payment service
			// where the health endpoint response is wrapped in a data object
			if (res.data.status === 'ok' || res.data.data.status === 'ok') {
				return { online: true }
			}
		} catch (e) {
			// Do nothing
		}

		return { online: false }
	}

	async getPublishableKey(): Promise<StripeConfig> {
		const res = await this.client.get(`payment/${this.version}/payment/config`)

		return res.data.data
	}

	/**
	 * Creates a Stripe Payment Intent for the specified order.
	 * Fails if the order is not in a payable state, or if the booking of seats fails.
	 * Confirms immediately if the order only contains free items.
	 *
	 * @param orderData contains the order id and the desired payment type
	 * @throws `BadRequestError`
	 */
	async createStripePaymentForOrder(
		orderData: CreatePaymentQuery
	): Promise<PaymentIntentResponse> {
		const query = queryString.stringify({
			order_id: orderData.orderId,
			type: orderData.type,
		})

		const res = await this.client.post(
			`payment/${this.version}/payment/${PaymentProvider.Stripe}?${query}`,
			{}
		)

		return res.data.data
	}

	/**
	 * Creates a payment intent with a custom amount.
	 * Throws for zero and below as amounts.
	 *
	 * @param data contains the order id and the desired payment type
	 * @throws `BadRequestError`
	 */
	async createStripePayment(
		data: PaymentAmount<CreatePaymentQuery>
	): Promise<PaymentIntentResponse> {
		const res = await this.client.post(
			`payment/${this.version}/payment/${PaymentProvider.Stripe}/custom`,
			data
		)

		return res.data.data
	}

	/**
	 * Creates a crypto payment for the specified order.
	 * Fails if the order is not in a payable state, or if the booking of seats fails.
	 * Confirms immediately if the order only contains free items.
	 *
	 * @param orderData contains the order id and the desired redirection URLs
	 * @throws `BadRequestError`
	 */
	async createCryptoPaymentForOrder(
		orderData: CreatePaymentQuery,
		meta: CryptoPaymentMeta = {}
	): Promise<CryptoPayment> {
		const query = queryString.stringify({
			order_id: orderData.orderId,
			type: orderData.type,
		})

		const res = await this.client.post(
			`payment/${this.version}/payment/${PaymentProvider.Coinbase}?${query}`,
			meta
		)

		return res.data.data
	}

	/**
	 * Creates a crypto payment with a custom amount.
	 * Throws for zero and below as amounts.
	 *
	 * @param data contains the amounts and currencies, with metadata
	 * @throws `BadRequestError`
	 */
	async createCryptoPayment(
		data: PaymentAmount<CryptoPaymentMeta>
	): Promise<CryptoPayment> {
		const res = await this.client.post(
			`payment/${this.version}/payment/${PaymentProvider.Coinbase}/custom`,
			data
		)

		return res.data.data
	}

	/**
	 * Creates a payment for an order.
	 * Uses OpenPayment/Hobex for the payment.
	 *
	 * @param data contains the amount and additional optional metadata, if you want to prefill the payment form
	 * @returns the payment id and timestamp, which can be used for instantiating the payment form
	 */
	async createOpenPaymentForOrder(
		order: OpenPaymentOrderQuery,
		data: OpenPaymentOrderData
	): Promise<OpenPaymentResponse> {
		const query = queryString.stringify({
			order_id: order.orderId,
		})
		const res = await this.client.post(
			`payment/${this.version}/payment/${PaymentProvider.OpenPayment}?${query}`,
			data
		)

		return res.data.data
	}

	/**
	 * Creates a payment intent with a custom amount.
	 * Uses OpenPayment/Hobex for the payment.
	 * Throws for zero and below as amounts.
	 *
	 * @param data contains the amount and additional optional metadata, if you want to prefill the payment form
	 * @returns the payment id and timestamp, which can be used for instantiating the payment form
	 */
	async createOpenPayment(
		data: PaymentAmount<OpenPaymentMeta>
	): Promise<OpenPaymentResponse> {
		const res = await this.client.post(
			`payment/${this.version}/payment/${PaymentProvider.OpenPayment}/custom`,
			data
		)

		return res.data.data
	}

	/**
	 * Create a PayPal payout. Only available to the orderbook service ATM.
	 *
	 * @param data contains the data for the emails and amounts to pay out
	 * @throws `BadRequestError`
	 */
	async createPayout(data: IPayoutData): Promise<IPayout> {
		const res = await this.client.post(
			`payment/${this.version}/payment/paypal/payout`,
			data
		)

		return res.data.data
	}
}
