import {
  ContractPointer,
  NFTCollectionMetadata,
  NFTCollectionSales,
  Value,
} from '../../../../../types';
import { RestfulProvider } from '../../RestfulProvider';
import { NftCollectionDataProvider } from '../NftCollectionDataProvider';
import { numberToValue } from '../../../../../utils/numberToValue';
import { chainToBlockchainExplorerHost } from '../../../../../utils/crossChainSupport/chainToBlockchainExplorerHost';
import { Chain, Timeframe } from '../../../../../enums';

// Docs: https://docs.reservoir.tools/

export class Reservoir extends RestfulProvider
  implements NftCollectionDataProvider {
  constructor(_config: any) {
    const apiHost = 'https://api.reservoir.tools/';
    super(_config, apiHost);
  }

  // @see https://docs.reservoir.tools/reference/getstatsv2
  // https://api.reservoir.tools/stats/v2
  async getFloor(_contract: ContractPointer): Promise<Value> {
    const host = this.getHost(_contract.chain);
    const uri = `${host}stats/v2/`;
    const options = {
      searchParams: {
        collection: _contract.address,
      },
      headers: {
        Accept: 'application/json',
        'x-api-key': this.getApiKey(),
      },
    };

    // Reservoir doesn't use the last lowest sale price for floor, but rather
    // the average of the top bid and floor ask.
    const json: any = await this.gotJson(uri, options);
    const ask = Number(json.stats.market.floorAsk.price.amount.decimal);
    const bid = Number(json.stats.market.topBid.price.amount.decimal);
    const floor: number = (ask + bid) / 2;

    const currencyInfo = this.getCurrencyInfoFromChain(_contract.chain);
    return numberToValue(floor, currencyInfo);
  }

  async getFloorChart(
    _contract: ContractPointer,
    _timeframe?: Timeframe
  ): Promise<any> {
    throw new Error('Method not implemented.');
  }

  async getHistoricSales(
    _contract: ContractPointer
  ): Promise<NFTCollectionSales> {
    throw new Error('Method not implemented.');
  }

  async getMarketCap(_contract: ContractPointer): Promise<Value> {
    throw new Error('Method not implemented.');
  }

  // https://docs.reservoir.tools/reference/getcollectionsv5
  // https://api.reservoir.tools/collections/v5?id={contractAddress}
  async getMetadata(_contract: ContractPointer): Promise<any> {
    const host = this.getHost(_contract.chain);
    const uri = `${host}collections/v5`;
    const options = {
      searchParams: {
        id: _contract.address,
      },
      headers: {
        Accept: 'application/json',
        'x-api-key': this.getApiKey(),
      },
    };
    const json: any = await this.gotJson(uri, options);
    const collection = json.collections[0];

    const currencyInfo = this.getCurrencyInfoFromChain(_contract.chain);
    const metadata: NFTCollectionMetadata = {
      contract: _contract,
      name: collection.name,
      description: collection.description,
      createdAt: collection.createdAt,
      openseaVerificationStatus:
        collection.openseaVerificationStatus === 'verified' ? true : false,
      openseaSlug: collection.slug,
      collectionSize: Number(collection.tokenCount),
      onSaleCount: Number(collection.onSaleCount),
      ownerCount: Number(collection.ownerCount),
      contractType: collection.contractKind,
      images: {
        thumbnail: collection.image,
        banner: collection.banner,
        samples: collection.sampleImages,
      },
      urls: {
        explorer: `${chainToBlockchainExplorerHost(_contract.chain)}/address/${
          _contract.address
        }`,
        website: collection.externalUrl,
        discord: collection.discordUrl,
        twitter: `https://twitter.com/${collection.twitterUsername}`,
      },
      stats: {
        currencyInfo: currencyInfo,
        floor: {
          h24: numberToValue(Number(collection.floorSale['1day']), currencyInfo)
            .amount,
          h24Change: numberToValue(
            Number(collection.floorSaleChange['1day']),
            currencyInfo
          ).amount.decimal,
          d7: numberToValue(Number(collection.floorSale['7day']), currencyInfo)
            .amount,
          d7Change: numberToValue(
            Number(collection.floorSaleChange['7day']),
            currencyInfo
          ).amount.decimal,
          d30: numberToValue(
            Number(collection.floorSale['30day']),
            currencyInfo
          ).amount,
          d30Change: numberToValue(
            Number(collection.floorSaleChange['30day']),
            currencyInfo
          ).amount.decimal,
        },
        volume: {
          h24: numberToValue(Number(collection.volume['1day']), currencyInfo)
            .amount,
          h24Change: numberToValue(
            Number(collection.volumeChange['1day']),
            currencyInfo
          ).amount.decimal,
          d7: numberToValue(Number(collection.volume['7day']), currencyInfo)
            .amount,
          d7Change: numberToValue(
            Number(collection.volumeChange['7day']),
            currencyInfo
          ).amount.decimal,
          d30: numberToValue(Number(collection.volume['30day']), currencyInfo)
            .amount,
          d30Change: numberToValue(
            Number(collection.volumeChange['30day']),
            currencyInfo
          ).amount.decimal,
        },
      },
    };

    return metadata;
  }

  getHost(_chain?: Chain): string {
    switch (_chain) {
      case undefined:
      case Chain.ETHEREUM:
        return this.getApiHost();
      case Chain.POLYGON:
        return 'https://api-polygon.reservoir.tools/';
      case Chain.BSC:
        return 'https://api-bsc.reservoir.tools/';
      case Chain.ARBITRUM:
        return 'https://api-arbitrum.reservoir.tools/';
      case Chain.OPTIMISM:
        return 'https://api-optimism.reservoir.tools/';
      default:
        throw new Error(`${_chain} is not supported by ${this.getName()}.`);
    }
  }

  getName(): string {
    return 'Reservoir';
  }
}
