import { Wallet } from "ethers";
import { JsonRpcBatchProvider } from "@ethersproject/providers";
import axios from "axios";

import {
  Result,
  Listing,
  CollectionBid,
  Buy,
  Sale,
  Order,
  Nft,
  OrderConfig,
  ProjectStats,
} from "./types";
import {
  getWavaxBalance,
  getWavaxApprovalStatus,
  approveWavax,
  getNftApprovalStatus,
  approveNft,
  handleSignAndExecute,
  handleSignTransaction,
} from "./utils/helpers";

import { PaginationConfig } from "./types";

export class Hyperspace {
  apiKey: string;
  wallet: Wallet;
  provider: JsonRpcBatchProvider;
  gasPriceMultiplier: number = 1;
  gasLimitMultiplier: number = 1;

  constructor(apiKey: string, rpcHttpUrl: string, privateKey: string) {
    this.apiKey = apiKey;
    this.provider = new JsonRpcBatchProvider(rpcHttpUrl);
    const wallet = new Wallet(`0x${privateKey}`);
    this.wallet = wallet.connect(this.provider);
  }

  getGasPriceMultiplier(): number {
    return this.gasPriceMultiplier;
  }

  updateGasPriceMultiplier(
    newGasPriceMultiplier: number
  ): Result<number, Error> {
    if (newGasPriceMultiplier < 1 || newGasPriceMultiplier > 20) {
      return {
        success: false,
        error: new Error("Gas price multiplier must be between 1 and 10"),
      };
    }

    this.gasPriceMultiplier = newGasPriceMultiplier;

    return {
      success: true,
      value: newGasPriceMultiplier,
    };
  }

  getGasLimitMultiplier(): number {
    return this.gasLimitMultiplier;
  }

  updateGasLimitMultiplier(
    newGasLimitMultiplier: number
  ): Result<number, Error> {
    if (newGasLimitMultiplier < 1 || newGasLimitMultiplier > 20) {
      return {
        success: false,
        error: new Error("Gas limit multiplier must be between 1 and 10"),
      };
    }

    this.gasLimitMultiplier = newGasLimitMultiplier;

    return {
      success: true,
      value: newGasLimitMultiplier,
    };
  }

  public async getListingsForProject(
    projectId?: string,
    paginationInfo?: PaginationConfig
  ): Promise<Result<Listing[], Error>> {
    const data = JSON.stringify({
      condition: {
        project_ids: [
          {
            project_id: projectId,
          },
        ],
        listing_type: "NORMAL",
      },
      order_by: {
        field_name: "listing_display_price",
        sort_order: "ASC",
      },
      pagination_info: paginationInfo,
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-collection-view",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to send request to fetch listings: ${error}`),
      };
    }

    const listings: Listing[] = response.data.collection_view_data;
    if (!listings) {
      return {
        success: false,
        error: new Error("Unable to find listings"),
      };
    }

    return {
      success: true,
      value: listings,
    };
  }

  /**
   * Fetch listings for a specific collection.
   * Right now, it ONLY fetches in ASCENDING order of price.
   * @param projectId Project ID of the collection to fetch listings for.
   * @param quantity Maximum number of listings to fetch.
   * @returns Array of listings or error.
   */
  // TODO: Is `quantity` really necessary?
  public async getListingsForUser(
    userAddress?: string,
    projectId?: string,
    paginationInfo?: PaginationConfig
  ): Promise<Result<Listing[], Error>> {
    const data = JSON.stringify({
      condition: {
        owner: userAddress ? userAddress : this.wallet.address,
        project_ids: [
          {
            project_id: projectId,
          },
        ],
        listing_type: "NORMAL",
      },
      order_by: {
        field_name: "listing_display_price",
        sort_order: "ASC",
      },
      pagination_info: paginationInfo,
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-collection-view",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to send request to fetch listings: ${error}`),
      };
    }

    const listings: Listing[] = response.data.collection_view_data;
    if (!listings) {
      return {
        success: false,
        error: new Error("Unable to find listings"),
      };
    }

    return {
      success: true,
      value: listings,
    };
  }

  /**
   * Get a specific listing.
   * @param collectionAddress Address of the collection to fetch listing for.
   * @param tokenId Token ID of the listing to fetch.
   * @returns Listing or error.
   */
  async getListing(
    collectionAddress: string,
    tokenId: string
  ): Promise<Result<Listing, Error>> {
    const data = JSON.stringify({
      condition: {
        token_addresses: [`${collectionAddress}_${tokenId}`],
      },
      order_by: {
        field_name: "listing_display_price",
        sort_order: "ASC",
      },
      pagination_info: {
        page_number: 1,
        page_size: 1,
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-collection-view",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to send request to fetch listing: ${error}`),
      };
    }

    const listing: Listing = response.data.collection_view_data[0];
    if (!listing) {
      return {
        success: false,
        error: new Error("Unable to find listing"),
      };
    }

    return {
      success: true,
      value: listing,
    };
  }

  /**
   * Fetch collection bids for a specific collection.
   * Right now, it ONLY outputs collection bids in DESCENDING order of price.
   * @param collectionAddress
   * @returns Array of collection bids or error.
   */
  async getCollectionBidsForProject(
    collectionAddress: string
  ): Promise<Result<CollectionBid[], Error>> {
    const data = JSON.stringify({
      condition: {
        contract_address: collectionAddress,
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-collection-bids-for-project",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to get collection bids for project: ${error}`
        ),
      };
    }

    const bids: CollectionBid[] = response.data.bids;
    if (!bids) {
      return {
        success: false,
        error: new Error("No bids found for project"),
      };
    }

    return {
      success: true,
      value: bids,
    };
  }

  async getCollectionBidsForProjectAndUser(
    collectionAddress: string,
    userAddress?: string
  ): Promise<Result<CollectionBid[], Error>> {
    if (collectionAddress === "") {
      throw new Error("Missing collection address");
    }
    if (!userAddress) {
      userAddress = this.wallet.address;
    }

    const data = JSON.stringify({
      condition: {
        contract_address: collectionAddress,
        buyer_address: userAddress,
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-collection-bids-for-project-and-user",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to get collection bids for project and user: ${error}`
        ),
      };
    }

    const bids: CollectionBid[] = response.data.bids;
    if (!bids) {
      return {
        success: false,
        error: new Error("No bids found for project"),
      };
    }

    return {
      success: true,
      value: bids,
    };
  }

  async getTokenRarityScore(
    collectionAddress: string,
    tokenId: string
  ): Promise<Result<number, Error>> {
    const getListingResult = await this.getListing(collectionAddress, tokenId);
    if (!getListingResult.success) {
      return {
        success: false,
        error: getListingResult.error,
      };
    }

    const listing = getListingResult.value;

    return {
      success: true,
      value: listing.data.rarity_snapshot.hyperspace,
    };
  }

  /**
   * List a specific NFT (executes on chain).
   * @param collectionAddress
   * @param tokenId
   * @param listingPriceAvax
   * @returns Order or error.
   */
  async list(
    collectionAddress: string,
    tokenId: string,
    listingPriceWei: number
  ): Promise<Result<Order, Error>> {
    const sellerAddress = this.wallet.address;
    const tokenAddress = `${collectionAddress}_${tokenId}`;

    let isApproved;
    try {
      isApproved = await getNftApprovalStatus(
        this.provider,
        sellerAddress,
        collectionAddress
      );
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to get NFT approval status: ${error}`),
      };
    }

    if (!isApproved) {
      try {
        await approveNft(this.wallet, collectionAddress);
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to approve NFT: ${error}`),
        };
      }
    }

    const data = JSON.stringify({
      condition: {
        list_tx_args: [
          {
            token_address: tokenAddress,
            seller_address: sellerAddress as string,
            metadata: {
              contractAddress: collectionAddress,
              tokenId,
              price: listingPriceWei.toString(),
            },
          },
        ],
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/create-list-tx",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to create list transaction: ${error}`
        ),
      };
    }

    const order = response.data[0].metadata;
    if (response.data?.length && order) {
      let handleSignTransactionResult;
      try {
        handleSignTransactionResult = await handleSignTransaction(
          order,
          this.wallet
        );
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to handle sign and execute: ${error}`),
        };
      }

      const signedOrder = JSON.parse(
        handleSignTransactionResult.transactionBlockBytes
      );

      try {
        await this.validateSignature(signedOrder);
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to validate signature: ${error}`),
        };
      }

      return {
        success: true,
        value: order,
      };
    } else {
      return {
        success: false,
        error: new Error(`Unable to create list transaction: ${response.data}`),
      };
    }
  }

  /**
   * Delist a specific NFT (executes on chain).
   * @param listing
   * @returns Transaction hash or error.
   */
  async delist(
    collectionAddress: string,
    price: number,
    metadata: any
  ): Promise<Result<string, Error>> {
    const data = JSON.stringify({
      condition: {
        delist_tx_args: [
          {
            seller_address: this.wallet.address,
            token_address: collectionAddress,
            price: (price * 10 ** 18).toString(),
            metadata: metadata,
          },
        ],
      },
    });

    const requestConfig = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/create-delist-tx",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestConfig);
    } catch (error: any) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to create delist transaction: ${error}`
        ),
      };
    }

    const encodedTransaction = response.data[0].byte_string;

    if (response.data?.length && encodedTransaction) {
      let transactionReceipt;
      try {
        transactionReceipt = await handleSignAndExecute(
          this.wallet,
          this.gasPriceMultiplier,
          this.gasLimitMultiplier,
          {
            data: encodedTransaction,
          }
        );
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to handle sign and execute`),
        };
      }

      if (transactionReceipt.transactionHash) {
        return {
          success: true,
          value: transactionReceipt.transactionHash,
        };
      } else {
        return {
          success: false,
          error: new Error(`Invalid transaction receipt`),
        };
      }
    } else {
      return {
        success: false,
        error: new Error(`Unable to create delist transaction`),
      };
    }
  }

  // async updateListing(
  //   collectionAddress: string,
  //   tokenId: string,
  //   newListingPriceWei: number
  // ) {
  //   const sellerAddress = this.wallet.address;
  //   const tokenAddress = `${collectionAddress}_${tokenId}`;

  //   let isApproved;
  //   try {
  //     isApproved = await getNftApprovalStatus(
  //       this.provider,
  //       sellerAddress,
  //       collectionAddress
  //     );
  //   } catch (error: unknown) {
  //     return {
  //       success: false,
  //       error: new Error(`Unable to get NFT approval status: ${error}`),
  //     };
  //   }

  //   const data = JSON.stringify({
  //     condition: {
  //       list_tx_args: [
  //         {
  //           token_address: tokenAddress,
  //           seller_address: sellerAddress as string,
  //           metadata: {
  //             contractAddress: collectionAddress,
  //             tokenId,
  //             price: newListingPriceWei.toString(),
  //           },
  //         },
  //       ],
  //     },
  //   });

  //   const requestArguments = {
  //     method: "post",
  //     url: "https://avax.api.hyperspace.xyz/rest/create-update-listing-tx",
  //     headers: {
  //       "Content-Type": "application/json",
  //       Authorization: this.apiKey,
  //     },
  //     data: data,
  //   };

  //   let response;
  //   try {
  //     response = await axios.request(requestArguments);
  //   } catch (error: unknown) {
  //     return {
  //       success: false,
  //       error: new Error(
  //         `Unable to send request to create update listing transaction: ${error}`
  //       ),
  //     };
  //   }

  //   console.log(response.data);
  // }

  /**
   * Creates a collection bid (), executes on chain
   * @param contractAddress
   * @param bidAmount
   * @returns
   */
  async collectionBid(
    contractAddress: string,
    bidAmountWei: number
  ): Promise<Result<Order, Error>> {
    const bidderAddress = this.wallet.address;

    const wavaxBalance = await getWavaxBalance(this.provider, bidderAddress);

    let isApproved;
    try {
      isApproved = await getWavaxApprovalStatus(
        this.provider,
        bidderAddress,
        wavaxBalance
      );
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to fetch NFT approval status: ${error}`),
      };
    }

    if (!isApproved) {
      try {
        await approveWavax(this.wallet);
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to approve NFT: ${error}`),
        };
      }
    }

    const data = JSON.stringify({
      condition: {
        price: bidAmountWei,
        buyer_address: bidderAddress,
        metadata: {
          contractAddress,
          price: bidAmountWei.toString(),
        },
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/create-collection-bid-tx",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to create collection bid transaction: ${error}`
        ),
      };
    }

    const order = response.data[0].metadata;
    if (response.data?.length && order) {
      let handleSignTransactionResult;
      try {
        handleSignTransactionResult = await handleSignTransaction(
          order,
          this.wallet
        );
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to handle sign and execute: ${error}`),
        };
      }

      const signedOrder = JSON.parse(
        handleSignTransactionResult.transactionBlockBytes
      );

      try {
        await this.validateSignature(signedOrder);
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to validate signature: ${error}`),
        };
      }

      return {
        success: true,
        value: order,
      };
    } else {
      return {
        success: false,
        error: new Error(
          `Unable to create collection bid transaction: ${response.data}`
        ),
      };
    }
  }

  /**
   * Cancel a collection bid (executes on-chain).
   * @param collection_bid
   * @returns Transaction hash or error.
   */
  async cancelCollectionBid(
    collectionBid: CollectionBid
  ): Promise<Result<string, Error>> {
    const data = JSON.stringify({
      condition: {
        buyer_address: this.wallet.address,
        price: collectionBid.price,
        metadata: collectionBid.metadata,
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/create-cancel-collection-bid-tx",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to create cancel collection bid transaction: ${error}`
        ),
      };
    }

    const encodedTransaction = response.data[0].byte_string;
    if (response.data?.length && encodedTransaction) {
      let transactionReceipt;
      try {
        transactionReceipt = await handleSignAndExecute(
          this.wallet,
          this.gasPriceMultiplier,
          this.gasLimitMultiplier,
          {
            data: encodedTransaction,
          }
        );
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to handle sign and execute: ${error}`),
        };
      }

      if (transactionReceipt.transactionHash) {
        return {
          success: true,
          value: transactionReceipt.transactionHash,
        };
      }

      return {
        success: false,
        error: new Error(`Invalid transaction receipt`),
      };
    } else {
      return {
        success: false,
        error: new Error(`Unable to create cancel collection bid transaction`),
      };
    }
  }

  async updateCollectionBid() {}

  /**
   * Buy (create buy transaction) a specific NFT on-chain.
   * @param collectionAddress
   * @param tokenId
   * @returns Transaction hash or error.
   */
  async buy(
    collectionAddress: string,
    tokenId: string
  ): Promise<Result<Buy, Error>> {
    const getListingResult = await this.getListing(collectionAddress, tokenId);
    if (!getListingResult.success) {
      return {
        success: false,
        error: getListingResult.error,
      };
    }

    const listing = getListingResult.value;
    const listingPriceAvax = listing.price.raw_price;
    const listingMetadata = listing.data.listing_snapshot.metadata;

    if (!listingPriceAvax) {
      return {
        success: false,
        error: new Error("Unable to find listing price"),
      };
    }
    if (!listingMetadata) {
      return {
        success: false,
        error: new Error("Unable to find listing metadata"),
      };
    }

    const buyTransactionArguments = [
      {
        buyer_address: this.wallet.address.toString(),
        token_address: listing.data.token_address,
        price: listingPriceAvax,
        metadata: listingMetadata,
      },
    ];

    const data = JSON.stringify({
      condition: {
        buy_tx_args: buyTransactionArguments,
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/create-buy-tx",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to create buy transaction: ${error}`
        ),
      };
    }

    const totalAmountToPay = [
      BigInt(listingMetadata.event_log.erc20TokenAmount),
      ...listingMetadata.event_log.fees.map((fee: any) => BigInt(fee.amount)),
    ].reduce((a, b) => a + b);

    if (response.data?.length && response.data[0].byte_string) {
      const encodedTransaction = response.data[0].byte_string;

      let transactionReceipt;
      try {
        transactionReceipt = await handleSignAndExecute(
          this.wallet,
          this.gasPriceMultiplier,
          this.gasLimitMultiplier,
          {
            data: encodedTransaction,
            value: totalAmountToPay,
          }
        );
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to handle sign and execute: ${error}`),
        };
      }

      const transactionHash = transactionReceipt.transactionHash;

      if (transactionReceipt.status == 1 && transactionHash) {
        return {
          success: true,
          value: {
            transactionHash: transactionReceipt.transactionHash,
            price: listingPriceAvax,
          },
        };
      }

      return {
        success: false,
        error: new Error(`Invalid transaction receipt`),
      };
    } else {
      return {
        success: false,
        error: new Error(`Unable to create buy transaction`),
      };
    }
  }

  /**
   * Sell (create accept collection bid transaction) a specific NFT on-chain.
   * By default, it will accept the highest bid for the NFT.
   * If you want to accept a specific bid, pass the bid amount as a parameter.
   * @param collectionAddress
   * @param tokenId
   * @returns Transaction hash, price, and fee or error.
   */
  async executeSell(
    collectionAddress: string,
    tokenId: string
    // bidAmount?: number
  ): Promise<Result<Sale, Error>> {
    const sellerAddress = this.wallet.address;

    let isApproved;
    try {
      isApproved = await getNftApprovalStatus(
        this.provider,
        sellerAddress,
        collectionAddress
      );
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to fetch NFT approval status: ${error}`),
      };
    }

    if (!isApproved) {
      try {
        await approveNft(this.wallet, collectionAddress);
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to approve NFT: ${error}`),
        };
      }
    }

    const getCollectionBidsForProjectResult =
      await this.getCollectionBidsForProject(collectionAddress);
    if (!getCollectionBidsForProjectResult.success) {
      return {
        success: false,
        error: getCollectionBidsForProjectResult.error,
      };
    }

    const bid = getCollectionBidsForProjectResult.value[0];
    const bidPrice = bid.price;
    const bidFee = bid.fee;
    const bidMetadata = bid.metadata;

    if (!bidPrice) {
      return {
        success: false,
        error: new Error(`No collection bid price found for highest bid`),
      };
    }
    if (!bidFee) {
      return {
        success: false,
        error: new Error(`No collection bid fee found for highest bid`),
      };
    }
    if (!bidMetadata) {
      return {
        success: false,
        error: new Error(`No collection bid metadata found for highest bid`),
      };
    }

    const data = JSON.stringify({
      condition: {
        token_address: `${collectionAddress}_${tokenId}`,
        price: bidPrice,
        seller_address: sellerAddress,
        metadata: bidMetadata,
      },
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/create-accept-collection-bid-tx",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to create accept collection bid transaction: ${error}`
        ),
      };
    }

    if (response.data?.length && response.data[0].byte_string) {
      const encodedTransaction = response.data[0].byte_string;

      let transactionReceipt;
      try {
        transactionReceipt = await handleSignAndExecute(
          this.wallet,
          this.gasPriceMultiplier,
          this.gasLimitMultiplier,
          {
            data: encodedTransaction,
          }
        );
      } catch (error: unknown) {
        return {
          success: false,
          error: new Error(`Unable to handle sign and execute: ${error}`),
        };
      }

      const transactionHash = transactionReceipt.transactionHash;

      if (transactionReceipt.status == 1 && transactionHash) {
        return {
          success: true,
          value: {
            transactionHash,
            price: bidPrice,
            fee: bidFee,
          },
        };
      }

      return {
        success: false,
        error: new Error(`Invalid transaction receipt`),
      };
    } else {
      return {
        success: false,
        error: new Error(`Unable to create accept collection bid transaction`),
      };
    }
  }

  async userBids(contractAddress?: string, tokenId?: string) {
    const userAddress = this.wallet.address;

    try {
      const data = JSON.stringify({
        condition: {
          owner: userAddress,
          listing_type: "NORMAL",
          project_ids: [
            {
              project_id: "c43f2070-8d90-40f2-a81a-d564889263ee",
            },
          ],
        },
      });

      const requestConfig = {
        method: "post",
        url: "https://avax.api.hyperspace.xyz/rest/get-marketplace-snapshots",
        headers: {
          "Content-Type": "application/json",
          Authorization: this.apiKey,
        },
        data: data,
      };
      const response = await axios.request(requestConfig);
      const marketplace_snapshots = response.data.marketplace_snapshots;

      console.log(marketplace_snapshots);
    } catch (error: any) {
      console.log(error);
      return {
        digest: null,
        errors: error.message,
      };
    }
  }

  async userListings() {
    const userAddress = this.wallet.address;

    // "condition": {
    //   "token_address": "string",
    //   "project_id": "string",
    //   "action_type": "string",
    //   "buyer_address": "string",
    //   "seller_address": "string",
    //   "marketplace_programs": [
    //     {}
    //   ],
    //   "populate_snapshot": true
    // }

    try {
      const data = JSON.stringify({
        condition: {
          buyer_address: userAddress,
        },
      });

      const requestConfig = {
        method: "post",
        url: "https://avax.api.hyperspace.xyz/rest/get-user-listings",
        headers: {
          "Content-Type": "application/json",
          Authorization: this.apiKey,
        },
        data: data,
      };

      const res = await axios.request(requestConfig);
    } catch (error: any) {
      console.log(error);
      return {
        digest: null,
        errors: error.message,
      };
    }
  }

  // TODO: Promise<Result<any, Error>>
  async validateSignature(signedOrder: any) {
    const data = JSON.stringify({
      order: signedOrder,
    });

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/validate-signature",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      throw new Error(`Unable to validate signature: ${error}`);
    }

    return response;
  }

  /**
   * Get all NFTs owned by a specific user.
   * If no user address is provided, it will fetch NFTs owned by this client's wallet address.
   */
  async getUserOwnedNfts(
    userAddress?: string,
    projectId?: string,
    pagination?: PaginationConfig
  ): Promise<Result<Nft[], Error>> {
    const data = JSON.stringify(
      projectId
        ? {
            condition: {
              owner: userAddress ? userAddress : this.wallet.address,
              project_ids: [
                {
                  project_id: projectId,
                },
              ],
            },
            pagination_info: pagination,
          }
        : {
            condition: {
              owner: userAddress,
            },
            pagination_info: pagination,
          }
    );

    const requestArguments = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-marketplace-snapshots",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data,
    };

    let response;
    try {
      response = await axios.request(requestArguments);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(`Unable to send request to fetch listings: ${error}`),
      };
    }

    return { success: true, value: response.data.marketplace_snapshots };
  }

  async getCollectionStats(
    projectId?: string,
    orderBy?: OrderConfig[],
    pagination?: PaginationConfig
  ): Promise<Result<ProjectStats[], Error>> {
    const data = JSON.stringify(
      projectId
        ? {
            condition: {
              project_ids: [projectId],
            },
            order_by: orderBy,
            pagination_info: pagination,
          }
        : {
            order_by: orderBy,
            pagination_info: pagination,
          }
    );
    const args = {
      method: "post",
      url: "https://avax.api.hyperspace.xyz/rest/get-project-stats",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data,
    };
    let res;
    try {
      res = await axios.request(args);
    } catch (error: unknown) {
      return {
        success: false,
        error: new Error(
          `Unable to send request to get collection stats: ${error}`
        ),
      };
    }

    return { success: true, value: res.data.project_stats };
  }

  async getCollectionActivity(
    collection: string,
    actionTypes?: string[],
    pagination?: PaginationConfig
  ) {
    const data = JSON.stringify({
      condition: {
        projects: [
          {
            project_id: collection,
          },
        ],
        action_types: actionTypes,
      },
      pagination_info: pagination,
    });

    const config = {
      method: "post",
      maxBodyLength: Infinity,
      url: "https://avax.api.hyperspace.xyz/rest/get-collection-activity",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    return (await axios.request(config)).data;
  }

  async getUserActivity(
    walletAddress: string,
    actionTypes?: string[],
    pagination?: PaginationConfig
  ) {
    const data = JSON.stringify({
      condition: {
        user_address: walletAddress,
        action_types: actionTypes,
      },
      pagination_info: pagination,
    });

    const config = {
      method: "post",
      maxBodyLength: Infinity,
      url: "https://avax.api.hyperspace.xyz/rest/get-user-activity",
      headers: {
        "Content-Type": "application/json",
        Authorization: this.apiKey,
      },
      data: data,
    };

    return (await axios.request(config)).data;
  }
}
