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

import { HealthStatus, IdParam, ListInfo } from '../common/types'
import {
	NewNftOrder,
	Nft,
	NftBatchCreate,
	NftCollection,
	NftCollectionCreate,
	NftCollectionQuery,
	NftCollectionUpdate,
	NftCreate,
	NftMint,
	NftOrder,
	NftOrderCreate,
	NftQuery,
	NftUpdate,
	OrderQuery,
	SingleNftCollectionQuery,
	SingleNftQuery,
} from './types'
import { buildQuery } from '../common/query'

/**
 * Service class for NFT API calls.
 */
export class NftService {
	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(`nft/health`)
			if (res.data.status === 'ok') {
				return { online: true }
			}
		} catch (e) {
			// Do nothing
		}

		return { online: false }
	}

	/**
	 * Returns all nfts for the logged in user
	 */
	async getMyNfts(): Promise<Nft[]> {
		const res = await this.client.get(`nft/${this.version}/my_nfts`)

		return res.data.data.nfts
	}

	/**
	 * Returns all nfts with the given query
	 */
	async listNfts(req: NftQuery): Promise<ListInfo<Nft>> {
		// Only fetch nft attributes if the with parameter is not defined
		const relationsFetched = req.with ?? { attribute: true }

		const query = queryString.stringify(
			{
				...req,
				with: buildQuery(relationsFetched),
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(`nft/${this.version}/nft?${query}`)

		return res.data
	}

	/**
	 * Returns a specific NFT with the given relations
	 */
	async getNft(req: SingleNftQuery): Promise<Nft> {
		// Only fetch nft attributes if the with parameter is not defined
		const relationsFetched = req.with ?? { attribute: true }

		const query = queryString.stringify(
			{ with: buildQuery(relationsFetched) },
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(
			`nft/${this.version}/nft/${req.id}?${query}`
		)

		return res.data.data
	}

	async createNft(nft: NftCreate): Promise<Nft> {
		const res = await this.client.post(`nft/${this.version}/nft`, nft)
		return res.data.data
	}

	async updateNft(nft: NftUpdate): Promise<Nft> {
		const res = await this.client.patch(
			`nft/${this.version}/nft/${nft.id}`,
			nft
		)
		return res.data.data
	}

	async deleteNft(id: IdParam) {
		await this.client.delete(`nft/${this.version}/nft/${id.id}`)
	}

	async mintNft(req: NftMint): Promise<void> {
		await this.client.post(
			`nft/${this.version}/chaincode/${req.chaincodeId}/nft/${req.nftId}/mint`,
			{ account: req.account }
		)
	}

	/**
	 * Returns all collections with the given query
	 */
	async listCollections(
		req: NftCollectionQuery
	): Promise<ListInfo<NftCollection>> {
		const query = queryString.stringify(
			{
				...req,
				with: req.with ? buildQuery(req.with) : undefined,
			},
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(`nft/${this.version}/collection?${query}`)

		return res.data
	}

	/**
	 * Returns a specific NFT collection with the given relations
	 */
	async getCollection(req: SingleNftCollectionQuery): Promise<NftCollection> {
		const query = queryString.stringify(
			{ with: req.with ? buildQuery(req.with) : undefined },
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(
			`nft/${this.version}/collection/${req.id}?${query}`
		)

		return res.data.data
	}

	async createCollection(
		collection: NftCollectionCreate
	): Promise<NftCollection> {
		const res = await this.client.post(
			`nft/${this.version}/collection`,
			collection
		)
		return res.data.data
	}

	async updateCollection(
		collection: NftCollectionUpdate
	): Promise<NftCollection> {
		const res = await this.client.patch(
			`nft/${this.version}/collection/${collection.id}`,
			collection
		)
		return res.data.data
	}

	async deleteCollection(id: IdParam) {
		await this.client.delete(`nft/${this.version}/collection/${id.id}`)
	}

	async createOrder(order: NftOrderCreate): Promise<NewNftOrder> {
		const res = await this.client.post(`nft/${this.version}/order`, order)
		return res.data.data
	}

	async getOrder(req: IdParam): Promise<NftOrder> {
		const res = await this.client.get(`nft/${this.version}/order/${req.id}`)
		return res.data.data
	}

	async getOrders(req: OrderQuery): Promise<ListInfo<NftOrder>> {
		const query = queryString.stringify(
			{ with: req.with ? buildQuery(req.with) : undefined },
			{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true }
		)
		const res = await this.client.get(`nft/${this.version}/order?${query}`)
		return res.data
	}

	async createBatchNfts(req: NftBatchCreate) {
		const res = await this.client.post(
			`nft/${this.version}/collection/${req.collectionId}/nft/batch`,
			{
				nft: req.nft,
				quantity: req.quantity,
			}
		)
		return res.data.data
	}
}
