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

import { buildQuery, getStringifiedQuery } from '../common/query'
import {
	HealthStatus,
	IdParam,
	ListInfo,
	StatusResponse,
} from '../common/types'
import {
	Order,
	OrderRequest,
	OrderContact,
	OrderGet,
	OrderListQuery,
	RefundOrderBody,
} from './types'

/**
 * Service class for event API calls. Requires an organizer to be set (call to `useOrganizer`)
 */
export class OrderService {
	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(`event/health`)
			if (res.data.status === 'ok') {
				return { online: true }
			}
		} catch (e) {
			// Do nothing
		}

		return { online: false }
	}

	/**
	 * Creates a ticket order
	 *
	 * @param req `OrderRequest`
	 * @returns `Order`
	 */
	async createOrder(req: OrderRequest): Promise<Order> {
		let res = await this.client.post(`event/${this.version}/order`, req)

		const order = res.data.data

		if (res && req.orderContact) {
			res = await this.client.post(
				`event/${this.version}/order/${order.id}/order_contact`,
				req.orderContact
			)
		}

		return { ...order, orderContact: res.data.data }
	}

	/**
	 * Returns an order by it's ID
	 *
	 * @param req ID of the order
	 * @returns `Order`
	 */
	async getOrder(req: OrderGet): Promise<Order> {
		const query = queryString.stringify(
			{
				with: !req.with ? undefined : buildQuery(req.with),
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

		const res = await this.client.get(
			`event/${this.version}/order/${req.id}?${query}`
		)

		return res.data.data
	}

	/**
	 * Downloads all tickets associated with an order
	 *
	 * @param req Request contains the ID of the order
	 * @returns PDF document in binary format
	 */
	async downloadTickets(req: IdParam): Promise<any> {
		const res = await this.client.get(
			`event/${this.version}/order/${req.id}/download_tickets`,
			{
				responseType: 'blob',
			}
		)

		return res.data
	}

	/**
	 * Sends an email with the tickets belonging to the order.
	 *
	 * @param req Request contains the ID of the order
	 */
	async sendOrderEmail(req: IdParam): Promise<void> {
		await this.client.post(
			`event/${this.version}/order/${req.id}/email_tickets`,
			{}
		)
	}

	/**
	 * Sends an email with the tickets belonging to the order.
	 * Invocable by an organizer
	 *
	 * @param req Request contains the ID of the order
	 */
	async adminOrderEmailResend(req: IdParam): Promise<void> {
		await this.client.post(
			`event/${this.version}/order/${req.id}/admin_email_tickets`,
			{}
		)
	}

	/**
	 * Creates and assigns an order contact for a particular order
	 *
	 * @param orderId Id of the order
	 * @param orderContact Order contact details
	 * @returns Created order contact
	 */
	async createOrderContact(
		orderId: IdParam,
		orderContact: OrderContact
	): Promise<OrderContact> {
		const res = await this.client.post(
			`event/${this.version}/order/${orderId.id}/order_contact`,
			orderContact
		)

		return res.data.data
	}

	/**
	 * Get list of orders
	 * @param id Query orders by the order ID
	 * @param params.organizer_id Name of the organizer the order belongs to
	 * @param include_not_owned Set this to true if you want to list orders created by other users
	 * @param contact_email Query orders by the order contact email
	 * @param event_id Query orders by the event ID
	 * @returns List of orders with the paging information included
	 */
	async listOrders(params: OrderListQuery = {}): Promise<ListInfo<Order>> {
		const query = getStringifiedQuery(params)

		const res = await this.client.get(`event/${this.version}/order?${query}`)

		return res.data
	}

	/**
	 * Refund an order. Partially if you specify ticketIds, fully otherwise.
	 *
	 * @param req Order ID and tickets to refund
	 * @param body Optional list of tickets to refund instead of the whole order
	 * @returns Refund success status
	 */
	async refundOrder(
		id: IdParam,
		body: RefundOrderBody = {}
	): Promise<StatusResponse> {
		const res = await this.client.post(
			`event/${this.version}/order/${id.id}/refund`,
			body
		)

		return res.data.data
	}
}
