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

import { buildQuery, getStringifiedQuery } from '../common/query'
import {
	HealthStatus,
	IdParam,
	ListInfo,
	QRCodePayload,
	StringIdParam,
} from '../common/types'
import {
	BlockchainFunction,
	BlockchainFunctionParams,
} from '../blockchain/types'
import {
	TicketConfigId,
	Event,
	OrganizerSpecificId,
	NewEvent,
	OrganizerName,
	UpdatableEvent,
	TicketConfig,
	ListEventsQuery,
	SingleEventQuery,
	MyEventsQuery,
	PublicKey,
	TicketCount,
	QrCodeTicketConfig,
	CreateNftDrop,
	NftDrop,
	NftDropQuery,
	SingleNftDropQuery,
	UpdateNftDrop,
	NftDropFilterId,
	ListOrganizerEventsQuery,
	ListOrganizerEventTicketConfigsQuery,
	SingleOrganizerEventQuery,
	TicketDiscount,
	CreateTicketDiscount,
	UpdateTicketDiscount,
	SingleTicketDiscountQuery,
	CreateDiscountBatch,
	ListTicketDiscountQuery,
	TicketDiscountIdParam,
	CreatePromoSection,
	PromoSectionId,
	UpdatePromoSection,
	PromoSection,
	PrivateEventQuery,
	CreateTicketConfig,
	Sector,
	CreateSector,
	UpdateSector,
	SectorId,
	SingleSectorQuery,
	ListSectorQuery,
	Timeslot,
	CreateTimeslot,
	TimeslotId,
	UpdateTimeslot,
	SingleTimeslotQuery,
	ListTimeslotsQuery,
	ListInformationEmailQuery,
	InformationEmail,
	SingleInformationEmailQuery,
	CreateInformationEmail,
	UpdateInformationEmail,
	InformationEmailId,
	TicketFormat,
	Category,
	ListCategoryQuery,
	EventHolderList,
	ListQuestionnaireQuery,
	Questionnaire,
	CreateQuestionnaire,
	QuestionnaireQuery,
	UpdateQuestionnaire,
	ListQuestionQuery,
	Question,
	CreateQuestion,
	SendTicketsData,
	SendTicketsResponse,
} from './types'

/**
 * Service class for event API calls. Requires an organizer to be set (call to `useOrganizer`)
 */
export class EventService {
	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 }
	}

	/**
	 * Creates a new event. Subobjects except for the subcategory can be added later.
	 * `endAt` needs to be after `startAt` and both need to be in the future.
	 * After an event is created, it needs to be published to be available for purchase.
	 * To publish an event, it needs to have at least one ticket configuration.
	 *
	 * @returns new Event
	 */
	async createEvent(name: OrganizerName, newEvent: NewEvent): Promise<Event> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${name.name}/event`,
			newEvent
		)

		return res.data.data
	}

	/**
	 * Updates existing event.
	 * @param id Org ID and event ID of the event you want to update
	 * @param updatedEventFields Fields on the event to be updated
	 * @returns new Event
	 */
	async updateEvent(
		id: OrganizerSpecificId,
		updatedEventFields: UpdatableEvent
	): Promise<Event> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}`,
			updatedEventFields
		)

		return res.data.data
	}

	/**
	 * Deletes existing event.
	 * @param id Org ID and event ID of the event you want to delete
	 */
	async deleteEvent(id: OrganizerSpecificId) {
		const res = await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}`
		)

		return res.data.data
	}

	/**
	 * Cancels existing event.
	 * @param id Org ID and event ID of the event you want to cancel
	 */
	async cancelEvent(id: OrganizerSpecificId) {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/cancel`
		)

		return res.data.data
	}

	/**
	 * @param id Org ID and event ID of the event you want to publish
	 * @returns Publish event payload
	 */
	async getEventPublishingPayload(
		id: OrganizerSpecificId
	): Promise<BlockchainFunctionParams> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/chaincode_payload`
		)
		return {
			fcn: BlockchainFunction.CreateEvent,
			args: res.data.data,
		}
	}

	/**
	 * Returns payload when creating an event on the blockchain.
	 *
	 * @param id Org ID and event ID of the event you want to publish
	 * @returns payload to be when creating it on the blockchain
	 */
	async getEventPublishPayload(id: OrganizerSpecificId): Promise<Event> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/chaincode/publish/proposal`
		)

		return res.data.data
	}

	/**
	 * Returns QR Code payload when creating an event on the blockchain.
	 *
	 * @param id Org ID and event ID of the event you want to publish
	 * @returns QR Code payload
	 */
	async getEventPublishQRCodePayload(
		id: OrganizerSpecificId
	): Promise<QRCodePayload> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/chaincode/publish/qrcode_payload`
		)

		return res.data.data
	}

	/**
	 * Returns payload when updating an event on the blockchain.
	 *
	 * @param id Org ID and event ID of the event you want to publish
	 * @returns payload when updating an even on the blockchain
	 */
	async getEventUpdatePayload(id: OrganizerSpecificId): Promise<Event> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/chaincode/update/proposal`
		)

		return res.data.data
	}

	/**
	 * Returns QR Code payload when updating an event on the blockchain.
	 *
	 * @param id Org ID and event ID of the event you want to publish
	 * @returns QR Code payload
	 */
	async getEventUpdateQRCodePayload(
		id: OrganizerSpecificId
	): Promise<QRCodePayload> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/chaincode/update/qrcode_payload`
		)

		return res.data.data
	}

	/**
	 * Returns the public key for ticket signature validation
	 *
	 * @param id Org ID and event ID of the tickets
	 * @returns Public key for signature validation
	 */
	async getTicketSigningKey(id: OrganizerSpecificId): Promise<PublicKey> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/public_key`
		)

		return res.data.data
	}

	/**
	 * Returns an event by its ID.
	 *
	 * @param req ID and additional query params
	 * @returns Event object
	 */
	async getEvent(req: SingleEventQuery): Promise<Event> {
		const query = queryString.stringify(
			{
				include_occurrences: req.include_occurrences,
				q: req.query,
				public: true,
				with: !req.with
					? '[ticket_config,ticket_discount,subcategory,ticket_format,timeslot,occurrence.ticket_config,sector]'
					: buildQuery(req.with),
				organizer_id: req.organizer_id,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

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

		return res.data.data
	}

	/**
	 * Returns an unpublished event by its ID.
	 *
	 * @param req ID and access_token of the event and additional query params
	 * @returns Event object
	 */
	async getPrivateEvent(req: PrivateEventQuery): Promise<Event> {
		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}/event/${req.id}/private/${req.access_token}?${query}`
		)

		return res.data.data
	}

	/**
	 * Returns a list of active events.
	 *
	 * @param req.q String value for text-based search on event
	 * @param req.start_at Date range where the event start_at field is filtered
	 * @param req.include_occurrences If true, in case of recurring events it includes occurrences
	 * @param req.with Field selector query attribute
	 * @returns
	 */
	async listEvents(req: ListEventsQuery = {}): Promise<ListInfo<Event>> {
		const query = queryString.stringify(
			{
				include_occurrences: req.include_occurrences,
				q: req.query,
				public: true,
				with: !req.with ? undefined : buildQuery(req.with),
				start_at:
					req.start_at !== undefined
						? `${req.start_at[0].toISOString()};${req.start_at[1].toISOString()}`
						: undefined,
				organizer_id: req.organizer_id,
				sort_by: req.sort_by,
				direction: req.direction,
				future_events: req.future_events,
				name: req.name,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

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

		return res.data
	}

	async listOrganizerEvents(
		id: OrganizerName,
		req: ListOrganizerEventsQuery = {}
	): Promise<ListInfo<Event>> {
		const query = queryString.stringify(
			{
				...req,
				with: req?.with ? buildQuery(req.with) : undefined,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.name}/event?${query}`
		)

		return res.data
	}

	async getOrganizerEvent(
		id: OrganizerSpecificId,
		req: SingleOrganizerEventQuery = {}
	): Promise<Event> {
		const query = queryString.stringify(
			{
				...req,
				with: req.with ? buildQuery(req.with) : undefined,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

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

		return res.data.data
	}

	async listOrganizerEventTicketConfigs(
		id: OrganizerSpecificId,
		req: ListOrganizerEventTicketConfigsQuery = {}
	): Promise<ListInfo<TicketConfig>> {
		const query = queryString.stringify(
			{
				...req,
				with: req.with ? buildQuery(req.with) : undefined,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/ticket_config?${query}`
		)

		return res.data
	}

	/**
	 * Returns a ticket configuration.
	 *
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config
	 * @returns
	 */
	async getTicketConfig(id: TicketConfigId): Promise<TicketConfig> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}`
		)

		return res.data.data
	}

	/**
	 * Returns newly created ticket configuration.
	 *
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.id ID of the event to which the ticket config belongs to
	 * @param ticketConfig data based on which ticket config should be created
	 * @returns new ticket config
	 */
	async createTicketConfig(
		id: OrganizerSpecificId,
		ticketConfig: CreateTicketConfig
	): Promise<TicketConfig> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/ticket_config`,
			ticketConfig
		)

		return res.data.data
	}

	/**
	 * Returns updated ticket configuration.
	 *
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config to be updated
	 * @param ticketConfig data based on which ticket config should be updated
	 * @returns new ticket config
	 */
	async updateTicketConfig(
		id: TicketConfigId,
		ticketConfig: Partial<CreateTicketConfig>
	): Promise<TicketConfig> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}`,
			ticketConfig
		)

		return res.data.data
	}

	/**
	 * Deletes ticket config.
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config to be deleted
	 */
	async deleteTicketConfig(id: TicketConfigId) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}`
		)
	}

	/**
	 * Returns payload when publishing a ticket config on the blockchain.
	 *
	 * @param id Org ID, event ID and ID of the ticket config you want to publish
	 * @returns payload when publishing a ticket config on the blockchain.
	 */
	async getTicketConfigPublishPayload(
		id: TicketConfigId
	): Promise<TicketConfig> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/chaincode/publish/payload`
		)

		return res.data.data
	}

	/**
	 * Returns QR Code payload when publishing a ticket config on the blockchain.
	 *
	 * @param id Org ID, event ID and ID of the ticket config you want to publish
	 * @returns QR Code payload
	 */
	async getTicketConfigPublishQRCodePayload(
		id: TicketConfigId
	): Promise<QRCodePayload> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/chaincode/publish/qrcode_payload`
		)

		return res.data.data
	}

	/**
	 * Returns payload when updating a ticket config on the blockchain.
	 *
	 * @param id Org ID, event ID and ID of the ticket config you want to update
	 * @returns payload when updating a ticket config on the blockchain.
	 */
	async getTicketConfigUpdatePayload(
		id: TicketConfigId
	): Promise<TicketConfig> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/chaincode/update/proposal`
		)

		return res.data.data
	}

	/**
	 * Returns QR Code payload when updating a ticket config on the blockchain.
	 *
	 * @param id Org ID, event ID and ID of the ticket config you want to update
	 * @returns QR Code payload
	 */
	async getTicketConfigUpdateQRCodePayload(
		id: TicketConfigId
	): Promise<QRCodePayload> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/chaincode/update/qrcode_payload`
		)

		return res.data.data
	}

	/**
	 * Returns events with secure tickets for the authorized enrolled user.
	 *
	 * @param req.date Filter events by `end_at` date comparing with `midnight`. Expected values are `future`, `past` and `all`.
	 * @returns
	 */
	async getMyEvents(req: MyEventsQuery) {
		const query = queryString.stringify({
			date: req.date,
		})

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

		return res.data.data
	}

	/**
	 * Returns ticket counts for a given event (one entry per ticket-configuration)
	 *
	 * @param req
	 * @returns
	 */
	async getTicketCount(req: SingleEventQuery): Promise<TicketCount[]> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${req.organizer_id}/event/${req.id}/statistics/validator/ticket_count`
		)

		return res.data.data
	}

	/**
	 * Returns all qr-codes (for unused tickets) for a given event
	 *
	 * @param req
	 * @returns
	 */
	async getQrCodes(req: SingleEventQuery): Promise<QrCodeTicketConfig[]> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${req.organizer_id}/event/${req.id}/qr_codes`
		)

		return res.data.data
	}

	async createNftDrop(nftDrop: CreateNftDrop): Promise<NftDrop> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${nftDrop.organizerId}/nft_drop`,
			nftDrop
		)
		return res.data.data
	}

	async updateNftDrop(nftDrop: UpdateNftDrop): Promise<NftDrop> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${nftDrop.organizerId}/nft_drop/${nftDrop.id}`,
			nftDrop
		)
		return res.data.data
	}

	async sendNftDrop(id: OrganizerSpecificId) {
		await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/nft_drop/${id.id}/send`
		)
	}

	async deleteNftDrop(id: OrganizerSpecificId) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/nft_drop/${id.id}`
		)
	}

	async deleteNftDropFilter(id: NftDropFilterId) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/nft_drop/${id.nftDropId}/nft_drop_filter/${id.id}`
		)
	}

	/**
	 * Returns a specific NFT collection with the given relations
	 */
	async getNftDrop(
		id: OrganizerSpecificId,
		req: SingleNftDropQuery = {}
	): Promise<NftDrop> {
		const query = queryString.stringify(
			{ with: req.with ? buildQuery(req.with) : undefined },
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/nft_drop/${id.id}?${query}`
		)

		return res.data.data
	}

	async listNftDrops(
		id: OrganizerName,
		req: NftDropQuery = {}
	): Promise<ListInfo<NftDrop>> {
		const query = queryString.stringify(
			{
				...req,
				with: !req.with ? undefined : buildQuery(req.with),
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

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

		return res.data
	}

	/**
	 * Creates a ticket discount.
	 * @param id.organizerId Name of the organizer to whom the ticket discount belongs to
	 * @param id.eventId ID of the event to which the ticket discount belongs to
	 * @returns new TicketDiscount
	 */
	async createTicketDiscount(
		id: OrganizerSpecificId,
		ticketDiscount: CreateTicketDiscount
	): Promise<TicketDiscount> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/ticket_discount`,
			ticketDiscount
		)
		return res.data.data
	}

	/**
	 * Updates ticket discount.
	 * @param id.organizerId Name of the organizer to whom the ticket discount belongs to
	 * @param id.eventId ID of the event to which the ticket discount belongs to
	 * @param id.id ID of the ticket discount to update
	 * @returns updated ticket discount
	 */
	async updateTicketDiscount(
		id: TicketDiscountIdParam,
		ticketDiscount: UpdateTicketDiscount
	): Promise<TicketDiscount> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_discount/${id.id}`,
			ticketDiscount
		)
		return res.data.data
	}

	/**
	 * Deletes ticket discount.
	 * @param id.organizerId Name of the organizer to whom the ticket discount belongs to
	 * @param id.eventId ID of the event to which the ticket discount belongs to
	 * @param id.id ID of the ticket discount to be deleted
	 */
	async deleteTicketDiscount(id: TicketDiscountIdParam) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_discount/${id.id}`
		)
	}

	/**
	 * Fetches ticket discount.
	 * @param id.organizerId Name of the organizer to whom the ticket discount belongs to
	 * @param id.eventId ID of the event to which the ticket discount belongs to
	 * @param id.id ID of the ticket discount to be fetched
	 */
	async getTicketDiscount(
		id: TicketDiscountIdParam,
		req: SingleTicketDiscountQuery = {}
	): Promise<TicketDiscount> {
		const query = queryString.stringify(
			{
				...req,
				with: req.with ? buildQuery(req.with) : undefined,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_discount/${id.id}?${query}`
		)

		return res.data.data
	}

	/**
	 * Creates discounts in batches.
	 * @param id.organizerId Name of the organizer to whom the ticket discount belongs to
	 * @param id.eventId ID of the event to which the ticket discount belongs to
	 * @param id.id ID of the ticket discount to be duplicated
	 */
	async createDiscountBatch(
		id: TicketDiscountIdParam,
		discountBatch: CreateDiscountBatch
	): Promise<TicketDiscount> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_discount/${id.id}/batch`,
			discountBatch
		)
		return res.data.data
	}

	async listTicketDiscounts(
		id: OrganizerSpecificId,
		req: ListTicketDiscountQuery = {}
	): Promise<ListInfo<TicketDiscount>> {
		const query = queryString.stringify(
			{
				...req,
				with: !req.with ? undefined : buildQuery(req.with),
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)

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

		return res.data
	}

	/**
	 * Create a single promo section
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.TicketConfigId ID of the ticket config which the promo section should belong to
	 * @param promoSection Created promo section
	 * @returns The created promo section
	 */
	async createPromoSection(
		id: TicketConfigId,
		promoSection: CreatePromoSection
	): Promise<PromoSection> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/promo_section`,
			promoSection
		)

		return res.data.data
	}

	/**
	 * Update a single promo section
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config which the promo section belongs to
	 * @param id.id ID of the promo section
	 * @param promoSection Updated promo section
	 * @returns The updated promo section
	 */
	async updatePromoSection(
		id: PromoSectionId,
		promoSection: UpdatePromoSection
	): Promise<PromoSection> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/promo_section/${id.id}`,
			promoSection
		)

		return res.data.data
	}

	/**
	 * Get a single promo section
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config which the promo section belongs to
	 * @param id.id ID of the promo section
	 * @returns Single promo section
	 */
	async getPromoSection(id: PromoSectionId): Promise<PromoSection> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/promo_section/${id.id}`
		)

		return res.data.data
	}

	/**
	 * List promo sections
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config which the promo section belongs to
	 * @returns List of the promo sections
	 */
	async listPromoSections(id: TicketConfigId): Promise<ListInfo<PromoSection>> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/promo_section`
		)

		return res.data
	}

	/**
	 * Delete a single promo section
	 * @param id.organizerId Name of the organizer to whom the ticket config belongs to
	 * @param id.eventId ID of the event to which the ticket config belongs to
	 * @param id.ticketConfigId ID of the ticket config which the promo section belongs to
	 * @param id.id ID of the promo section
	 */
	async deletePromoSection(id: PromoSectionId): Promise<void> {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/ticket_config/${id.ticketConfigId}/promo_section/${id.id}`
		)
	}

	/**
	 * Create a sector on an event.
	 * @param id.id ID of the event the sector should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param sector The sector that should be created
	 * @param sector.ticketConfig IDs of the ticket configs that should be applied to the sector
	 */
	async createSector(
		id: OrganizerSpecificId,
		sector: CreateSector
	): Promise<Sector> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/sector`,
			sector
		)
		return res.data.data
	}

	/**
	 * Update a sector on an event.
	 * @param id.eventId ID of the event which the sector belongs to
	 * @param id.organizerId Name of the organizer the event is belonging to
	 * @param id.id ID of the sector that should be updated
	 * @param sector.ticketConfig IDs of the ticket configs that should be applied to the sector
	 */
	async updateSector(id: SectorId, sector: UpdateSector): Promise<Sector> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/sector/${id.id}`,
			sector
		)
		return res.data.data
	}

	/**
	 * List sectors on an event
	 * @param id.id ID of the event the sector should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param query.with Allows fetching the associated ticket configs with the sector
	 */
	async listSectors(
		id: OrganizerSpecificId,
		query: ListSectorQuery = {}
	): Promise<ListInfo<Sector>> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${
				id.id
			}/sector?${getStringifiedQuery(query)}`
		)
		return res.data
	}

	/**
	 * Get a sector on an event
	 * @param id.eventId ID of the event which the sector belongs to
	 * @param id.organizerId Name of the organizer the event is belonging to
	 * @param id.id ID of the sector that should be fetched
	 * @param query Allows fetching the associated ticket configs with the sector
	 */
	async getSector(
		id: SectorId,
		query: SingleSectorQuery = {}
	): Promise<Sector> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${
				id.eventId
			}/sector/${id.id}?${getStringifiedQuery(query)}`
		)
		return res.data.data
	}

	/**
	 * Delete a sector from an event
	 * @param id.eventId ID of the event which the sector belongs to
	 * @param id.organizerId Name of the organizer the event is belonging to
	 * @param id.id ID of the sector that should be deleted
	 */
	async deleteSector(id: SectorId): Promise<void> {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/sector/${id.id}`
		)
	}

	/**
	 * Create a timeslot on an event.
	 * @param id.id ID of the event the timeslot should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param timeslot The timeslot that should be created
	 * @param timeslot.ticketConfig IDs of the ticket configs that should be applied to the timeslot
	 */
	async createTimeslot(
		id: OrganizerSpecificId,
		timeslot: CreateTimeslot
	): Promise<Timeslot> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/timeslot`,
			timeslot
		)
		return res.data.data
	}

	/**
	 * Update a timeslot on an event.
	 * @param id.eventId ID of the event which the timeslot belongs to
	 * @param id.organizerId Name of the organizer the event is belonging to
	 * @param id.id ID of the timeslot that should be updated
	 * @param timeslot.ticketConfig IDs of the ticket configs that should be applied to the timeslot
	 */
	async updateTimeslot(
		id: TimeslotId,
		timeslot: UpdateTimeslot
	): Promise<Timeslot> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/timeslot/${id.id}`,
			timeslot
		)
		return res.data.data
	}

	/**
	 * Get a timeslot on an event
	 * @param id.eventId ID of the event which the timeslot belongs to
	 * @param id.organizerId Name of the organizer the event is belonging to
	 * @param id.id ID of the timeslot that should be fetched
	 * @param query Allows fetching the associated ticket configs with the timeslot
	 */
	async getTimeslot(
		id: TimeslotId,
		query: SingleTimeslotQuery = {}
	): Promise<Timeslot> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${
				id.eventId
			}/timeslot/${id.id}?${getStringifiedQuery(query)}`
		)
		return res.data.data
	}

	/**
	 * Delete a timeslot from an event
	 * @param id.eventId ID of the event which the timeslot belongs to
	 * @param id.organizerId Name of the organizer the event is belonging to
	 * @param id.id ID of the timeslot that should be deleted
	 */
	async deleteTimeslot(id: TimeslotId): Promise<void> {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/timeslot/${id.id}`
		)
	}

	/**
	 * List timeslots on an event
	 * @param id.id ID of the event the timeslot should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param query.with Allows fetching the associated ticket configs with the timeslots
	 */
	async listTimeslots(
		id: OrganizerSpecificId,
		query: ListTimeslotsQuery = {}
	): Promise<ListInfo<Timeslot>> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${
				id.id
			}/timeslot?${getStringifiedQuery(query)}`
		)

		return res.data
	}

	/**
	 * List all emails in all states for an event.
	 * @param id.id ID of the event the emails should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param query.with Allows fetching the associated ticket configs for the information email filter
	 */
	async listInformationEmails(
		id: OrganizerSpecificId,
		query: ListInformationEmailQuery = {}
	): Promise<ListInfo<InformationEmail>> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${
				id.id
			}/information_email?${getStringifiedQuery(query)}`
		)

		return res.data
	}

	/**
	 * Get one information email.
	 * @param id.id ID of the event the emails should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param emailId ID of the information email that should be fetched
	 */
	async getInformationEmail(
		id: InformationEmailId,
		query: SingleInformationEmailQuery = {}
	): Promise<InformationEmail> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${
				id.eventId
			}/information_email/${id.id}?${getStringifiedQuery(query)}`
		)

		return res.data.data
	}

	/**
	 * Saves an information email.
	 * @param id.id ID of the event the emails should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param informationEmail Information email data that should be saved
	 */
	async saveInformationEmail(
		id: OrganizerSpecificId,
		informationEmail: CreateInformationEmail
	): Promise<InformationEmail> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/information_email`,
			informationEmail
		)
		return res.data.data
	}

	/**
	 * Update an information email.
	 * @param id.eventId ID of the event the emails should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param id.id ID of the information email that should be updated
	 * @param informationEmail Information email data that should be updated
	 */
	async updateInformationEmail(
		id: InformationEmailId,
		informationEmail: UpdateInformationEmail
	): Promise<InformationEmail> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/information_email/${id.id}`,
			informationEmail
		)
		return res.data.data
	}

	/**
	 * Send an information email to all recipients.
	 * @param id.eventId ID of the event the emails should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param id.id ID of the information email that should be sent
	 */
	async sendInformationEmail(id: InformationEmailId) {
		await this.client.post(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/information_email/${id.id}/send`
		)
	}

	/**
	 * Delete an information email.
	 * @param id.eventId ID of the event the emails should belong to
	 * @param id.organizerId Name of the organizer whom the event belongs to
	 * @param id.id ID of the information email that should be sent
	 */
	async deleteInformationEmail(id: InformationEmailId) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.eventId}/information_email/${id.id}`
		)
	}

	/**
	 * List ticket formats
	 * @returns Array of ticket formats
	 */
	async listTicketFormats(): Promise<ListInfo<TicketFormat>> {
		const res = await this.client.get(`event/${this.version}/ticket_format`)

		return res.data
	}

	/**
	 * Fetch ticket format
	 * @param id.id  ID of the ticket format
	 * @returns ticket format
	 */
	async getTicketFormat(id: IdParam): Promise<TicketFormat> {
		const res = await this.client.get(
			`event/${this.version}/ticket_format/${id.id}`
		)

		return res.data.data
	}

	/**
	 * List event categories
	 * @returns Array of categories
	 */
	async listEventCategories(
		req: ListCategoryQuery = {}
	): Promise<ListInfo<Category>> {
		const query = queryString.stringify(
			{
				...req,
				with: !req.with ? undefined : buildQuery(req.with),
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(`event/${this.version}/category?${query}`)

		return res.data
	}

	/**
	 * Returns a per-ticket-config array of enrollment IDs for ticket holders
	 *
	 * @param id Org ID and event ID of the tickets
	 * @returns Public key for signature validation
	 */
	async getHolderList(id: OrganizerSpecificId): Promise<EventHolderList> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/event/${id.id}/holders`
		)

		return res.data.data
	}
	/**
	 * Returns all questionnaires for given organizer
	 *
	 * @param id.id Organizer ID
	 */
	async listOrganizerQuestionnaires(
		id: StringIdParam,
		query: ListQuestionnaireQuery = {}
	): Promise<ListInfo<Questionnaire>> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${
				id.id
			}/questionnaire?${getStringifiedQuery(query)}`
		)

		return res.data
	}

	/**
	 * Creates new questionnaire
	 *
	 * @param id.id Organizer ID
	 * @param questionnaireData Data based on which new questionnaire is created
	 */
	async createQuestionnaire(
		id: StringIdParam,
		questionnaireData: CreateQuestionnaire
	): Promise<Questionnaire> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.id}/questionnaire`,
			questionnaireData
		)

		return res.data.data
	}

	/**
	 * Fetches queried questionnaire
	 *
	 * @param id.id Questionnaire ID
	 * @param id.organizerId Organizer ID
	 */
	async getOrganizerQuestionnaire(
		id: OrganizerSpecificId,
		query: QuestionnaireQuery = {}
	): Promise<Questionnaire> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/questionnaire/${
				id.id
			}?${getStringifiedQuery(query)}`
		)

		return res.data.data
	}

	/**
	 * Updates existing questionnaire
	 *
	 * @param id.id Questionnaire ID
	 * @param id.organizerId Organizer ID
	 * @param questionnaireData Data based on which questionnaire is updated
	 */
	async updateQuestionnaire(
		id: OrganizerSpecificId,
		questionnaireData: UpdateQuestionnaire
	): Promise<Questionnaire> {
		const res = await this.client.patch(
			`event/${this.version}/organizer/${id.organizerId}/questionnaire/${id.id}`,
			questionnaireData
		)

		return res.data.data
	}

	/**
	 * Deletes questionnaire with specified ID
	 *
	 * @param id.id Questionnaire ID
	 * @param id.organizerId Organizer ID
	 */
	async deleteQuestionnaire(id: OrganizerSpecificId) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/questionnaire/${id.id}`
		)
	}

	/**
	 * Fetches queried questionnaire
	 *
	 * @param id.id Questionnaire ID
	 */
	async getQuestionnaire(
		id: StringIdParam,
		query: QuestionnaireQuery = {}
	): Promise<Questionnaire> {
		const res = await this.client.get(
			`event/${this.version}/questionnaire/${id.id}?${getStringifiedQuery(
				query
			)}`
		)

		return res.data.data
	}

	/**
	 * Returns all questions for given organizer
	 *
	 * @param id.id Organizer ID
	 */
	async listOrganizerQuestions(
		id: StringIdParam,
		query: ListQuestionQuery = {}
	): Promise<ListInfo<Question>> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.id}/question?${getStringifiedQuery(
				query
			)}`
		)

		return res.data
	}

	/**
	 * Creates new question
	 *
	 * @param id.id Organizer ID
	 * @param questionData Data based on which new question is created
	 */
	async createQuestion(
		id: StringIdParam,
		questionData: CreateQuestion
	): Promise<Question> {
		const res = await this.client.post(
			`event/${this.version}/organizer/${id.id}/question`,
			questionData
		)

		return res.data.data
	}

	/**
	 * Fetches queried question
	 *
	 * @param id.id Question ID
	 * @param id.organizerId Organizer ID
	 */
	async getQuestion(id: OrganizerSpecificId): Promise<Question> {
		const res = await this.client.get(
			`event/${this.version}/organizer/${id.organizerId}/question/${id.id}`
		)

		return res.data.data
	}

	/**
	 * Deletes question with specified ID
	 *
	 * @param id.id Question ID
	 * @param id.organizerId Organizer ID
	 */
	async deleteQuestion(id: OrganizerSpecificId) {
		await this.client.delete(
			`event/${this.version}/organizer/${id.organizerId}/question/${id.id}`
		)
	}

	/**
	 * Sends tickets to users with provided emails
	 *
	 * @param ticketsData Data based on which the tickets are sent
	 */
	async sendTickets(ticketsData: SendTicketsData): Promise<SendTicketsResponse> {
		const res = await this.client.post(`event/${this.version}/ticket/send-group`, ticketsData);

		return res.data.data;
	}
}
