import {
  AccountAddress,
  Aptos,
  AptosConfig,
  MoveOption,
  Network,
  U64,
  type AccountAddressInput,
  type AptosSettings,
  type AnyRawTransaction,
  MoveVector,
  NetworkToNodeAPI,
  NetworkToIndexerAPI,
  NetworkToFaucetAPI,
  NetworkToPepperAPI,
  NetworkToProverAPI,
  InputGenerateTransactionData,
} from "@aptos-labs/ts-sdk";
import type {
  Role,
  SettleDiscount,
  OfferType,
  ConfigData,
  TokenConfigData,
  OfferData,
  OrderData,
} from "./type";
import { MAINNET, TESTNET } from "./constants";
import { EventCrawler } from "./eventCrawler";
import { InputTransactionData } from "@aptos-labs/wallet-adapter-react";
import { GraphQLConfig } from "./graphqlClient";
import { checkCoinToFa } from "./utils";

export class PreMarketAptos {
  aptos: Aptos;
  packageId: string;
  eventCrawler: EventCrawler;

  constructor(aptosConfig?: AptosSettings, graphQlConfig?: GraphQLConfig) {
    this.aptos = new Aptos(
      new AptosConfig({
        network: aptosConfig?.network ?? Network.MAINNET,
        fullnode: NetworkToNodeAPI[aptosConfig?.network ?? Network.MAINNET],
        indexer: NetworkToIndexerAPI[aptosConfig?.network ?? Network.MAINNET],
        faucet: NetworkToFaucetAPI[aptosConfig?.network ?? Network.MAINNET],
        pepper: NetworkToPepperAPI[aptosConfig?.network ?? Network.MAINNET],
        prover: NetworkToProverAPI[aptosConfig?.network ?? Network.MAINNET],
        ...aptosConfig,
      })
    );

    if (this.aptos.config.network === Network.MAINNET) {
      this.packageId = MAINNET.packageId;
    } else {
      this.packageId = TESTNET.packageId;
    }

    this.eventCrawler = new EventCrawler(
      this.aptos,
      this.packageId,
      graphQlConfig
    );
  }

  async getConfig(): Promise<ConfigData> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::whales_pre_market::get_config`,
      },
    });

    const configRaw = res as any;

    return {
      version: configRaw[0],
      owner: configRaw[1],
      discountVerifierPubkey: configRaw[2].bytes,
      pledgeRate: configRaw[3],
      feeSettle: configRaw[4],
      feeRefund: configRaw[5],
      feeWallet: configRaw[6],
      currentTokenIndex: configRaw[7],
    };
  }

  async getTokenConfig(
    tokenConfigId: AccountAddressInput
  ): Promise<TokenConfigData> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::whales_pre_market::get_token_config`,
        functionArguments: [tokenConfigId],
      },
    });

    const tokenConfigRaw = res as any;

    return {
      index: tokenConfigRaw[0],
      settleTime: tokenConfigRaw[1],
      settleDuration: tokenConfigRaw[2],
      status: tokenConfigRaw[3],
      settleRate: tokenConfigRaw[4],
      token: tokenConfigRaw[5]?.vec[0] || null,
    };
  }

  async getOffer(offerId: AccountAddressInput): Promise<OfferData> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::whales_pre_market::get_offer`,
        functionArguments: [offerId],
      },
    });

    const offerRaw = res as any;

    return {
      tokenId: offerRaw[0],
      exToken: offerRaw[1],
      exTokenStore: offerRaw[2],
      offerType: offerRaw[3],
      status: offerRaw[4],
      amount: offerRaw[5],
      value: offerRaw[6],
      filledAmount: offerRaw[7],
      collateral: offerRaw[8],
      fullMatch: offerRaw[9],
      owner: offerRaw[10],
    };
  }

  async getOrder(orderId: AccountAddressInput): Promise<OrderData> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::whales_pre_market::get_order`,
        functionArguments: [orderId],
      },
    });

    const orderRaw = res as any;

    return {
      tokenId: orderRaw[0],
      offerId: orderRaw[1],
      exToken: orderRaw[2],
      exTokenStore: orderRaw[3],
      status: orderRaw[4],
      amount: orderRaw[5],
      value: orderRaw[6],
      collateral: orderRaw[7],
      buyer: orderRaw[8],
      seller: orderRaw[9],
    };
  }

  async getRole(user: AccountAddressInput): Promise<Role> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::whales_pre_market::get_role`,
        functionArguments: [user],
      },
    });
    return res[0] as number as Role;
  }

  async isTokenAccepted(token: AccountAddressInput): Promise<boolean> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::whales_pre_market::is_token_accepted`,
        functionArguments: [token],
      },
    });
    return res[0] as boolean;
  }

  async buildUpdateConfig(
    owner: AccountAddressInput,
    params: {
      discountVerifierPubkey?: Uint8Array;
      pledgeRate?: number;
      feeSettle?: number;
      feeRefund?: number;
      feeWallet?: AccountAddressInput;
    }
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: owner,
      data: {
        function: `${this.packageId}::whales_pre_market::update_config`,
        functionArguments: [
          new MoveOption(
            params.discountVerifierPubkey
              ? MoveVector.U8(params.discountVerifierPubkey)
              : null
          ),
          new MoveOption(params.pledgeRate ? new U64(params.pledgeRate) : null),
          new MoveOption(params.feeSettle ? new U64(params.feeSettle) : null),
          new MoveOption(params.feeRefund ? new U64(params.feeRefund) : null),
          new MoveOption(
            params.feeWallet ? AccountAddress.from(params.feeWallet) : null
          ),
        ],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildMigrate(owner: AccountAddressInput): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: owner,
      data: {
        function: `${this.packageId}::whales_pre_market::migrate`,
        functionArguments: [],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildAddRole(
    admin: AccountAddressInput,
    user: AccountAddressInput,
    role: Role
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: admin,
      data: {
        function: `${this.packageId}::whales_pre_market::add_role`,
        functionArguments: [user, role],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildRemoveRole(
    admin: AccountAddressInput,
    user: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: admin,
      data: {
        function: `${this.packageId}::whales_pre_market::remove_role`,
        functionArguments: [user],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildAcceptExToken(
    operator: AccountAddressInput,
    token: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::whales_pre_market::accept_ex_token`,
        functionArguments: [token],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildRemoveExToken(
    operator: AccountAddressInput,
    token: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::whales_pre_market::remove_ex_token`,
        functionArguments: [token],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildCreateToken(
    admin: AccountAddressInput,
    tokenIndex: number,
    settleDuration: number
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: admin,
      data: {
        function: `${this.packageId}::whales_pre_market::create_token`,
        functionArguments: [tokenIndex.toString(), settleDuration.toString()],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildTokenToSettlePhase(
    operator: AccountAddressInput,
    tokenConfig: AccountAddressInput,
    token: AccountAddressInput,
    settleRate: number
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::whales_pre_market::token_to_settle_phase`,
        functionArguments: [tokenConfig, token, settleRate.toString()],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildUpdateSettleDuration(
    operator: AccountAddressInput,
    tokenConfig: AccountAddressInput,
    newDuration: number
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::whales_pre_market::update_settle_duration`,
        functionArguments: [tokenConfig, newDuration.toString()],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildTokenForceCancelSettlePhase(
    admin: AccountAddressInput,
    tokenConfig: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: admin,
      data: {
        function: `${this.packageId}::whales_pre_market::token_force_cancel_settle_phase`,
        functionArguments: [tokenConfig],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildCancelToken(
    admin: AccountAddressInput,
    tokenConfig: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: admin,
      data: {
        function: `${this.packageId}::whales_pre_market::cancel_token`,
        functionArguments: [tokenConfig],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildToggleTokenStatus(
    admin: AccountAddressInput,
    tokenConfig: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: admin,
      data: {
        function: `${this.packageId}::whales_pre_market::toggle_token_status`,
        functionArguments: [tokenConfig],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildForceCancelOrder(
    operator: AccountAddressInput,
    order: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::whales_pre_market::force_cancel_order`,
        functionArguments: [order],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildForceCancelOrders(
    operator: AccountAddressInput,
    orderList: AccountAddressInput[]
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::whales_pre_market::force_cancel_orders`,
        functionArguments: [orderList],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildCreateOffer(
    sender: AccountAddressInput,
    tokenConfig: AccountAddressInput,
    exToken: AccountAddressInput,
    offerType: OfferType,
    amount: number | bigint,
    value: number | bigint,
    fullMatch: boolean
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const { useCoin, coinType } = await checkCoinToFa(
      this.aptos,
      sender,
      exToken.toString()
    );

    let txData: InputGenerateTransactionData;

    if (useCoin) {
      txData = {
        sender: sender,
        data: {
          function: `${this.packageId}::whales_pre_market::create_offer_with_coin`,
          functionArguments: [
            tokenConfig,
            exToken,
            offerType,
            amount.toString(),
            value.toString(),
            fullMatch,
          ],
          typeArguments: [coinType],
        },
      };
    } else {
      txData = {
        sender: sender,
        data: {
          function: `${this.packageId}::whales_pre_market::create_offer`,
          functionArguments: [
            tokenConfig,
            exToken,
            offerType,
            amount.toString(),
            value.toString(),
            fullMatch,
          ],
        },
      };
    }

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildFillOffer(
    sender: AccountAddressInput,
    offer: AccountAddressInput,
    amount: number | bigint
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const offerData = await this.getOffer(offer);
    const { useCoin, coinType } = await checkCoinToFa(
      this.aptos,
      sender,
      offerData.exToken
    );

    let txData: InputGenerateTransactionData;

    if (useCoin) {
      txData = {
        sender: sender,
        data: {
          function: `${this.packageId}::whales_pre_market::fill_offer_with_coin`,
          functionArguments: [offer, amount.toString()],
          typeArguments: [coinType],
        },
      };
    } else {
      txData = {
        sender: sender,
        data: {
          function: `${this.packageId}::whales_pre_market::fill_offer`,
          functionArguments: [offer, amount.toString()],
        },
      };
    }

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildCancelOffer(
    sender: AccountAddressInput,
    offer: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: sender,
      data: {
        function: `${this.packageId}::whales_pre_market::cancel_offer`,
        functionArguments: [offer],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildCancelOfferWithDiscount(
    sender: AccountAddressInput,
    offer: AccountAddressInput,
    discount: SettleDiscount
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: sender,
      data: {
        function: `${this.packageId}::whales_pre_market::cancel_offer_with_discount`,
        functionArguments: [
          offer,
          discount.buyerDiscount.toString(),
          discount.sellerDiscount.toString(),
          discount.signature,
        ],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildSettleFilled(
    sender: AccountAddressInput,
    order: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: sender,
      data: {
        function: `${this.packageId}::whales_pre_market::settle_filled`,
        functionArguments: [order],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildSettleFilledWithDiscount(
    sender: AccountAddressInput,
    order: AccountAddressInput,
    discount: SettleDiscount
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: sender,
      data: {
        function: `${this.packageId}::whales_pre_market::settle_filled_with_discount`,
        functionArguments: [
          order,
          discount.buyerDiscount.toString(),
          discount.sellerDiscount.toString(),
          discount.signature,
        ],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildSettleCancelled(
    sender: AccountAddressInput,
    order: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: sender,
      data: {
        function: `${this.packageId}::whales_pre_market::settle_cancelled`,
        functionArguments: [order],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }

  async buildSettleCancelledWithDiscount(
    sender: AccountAddressInput,
    order: AccountAddressInput,
    discount: SettleDiscount
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: sender,
      data: {
        function: `${this.packageId}::whales_pre_market::settle_cancelled_with_discount`,
        functionArguments: [
          order,
          discount.buyerDiscount.toString(),
          discount.sellerDiscount.toString(),
          discount.signature,
        ],
      },
    };

    const rawTx = await this.aptos.transaction.build.simple(txData);

    return {
      rawTx,
      txData: txData as unknown as InputTransactionData,
    };
  }
}
