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, ConfigData, OtcOfferData } 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 OTCPreMarketAptos {
  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}::otc_pre_market::get_config`,
      },
    });

    const configRaw = res as any;

    return {
      version: configRaw[0],
      owner: configRaw[1],
      feePercent: configRaw[2],
      feeWallet: configRaw[3],
    };
  }

  async getOtcOffer(offerId: AccountAddressInput): Promise<OtcOfferData> {
    const res = await this.aptos.view({
      payload: {
        function: `${this.packageId}::otc_pre_market::get_otc_offer`,
        functionArguments: [offerId],
      },
    });

    const offerRaw = res as any;

    return {
      orderId: offerRaw[0],
      isBuyer: offerRaw[1],
      owner: offerRaw[2],
      exToken: offerRaw[3],
      value: offerRaw[4],
      deadline: offerRaw[5],
      status: offerRaw[6],
    };
  }

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

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

  async buildUpdateConfig(
    owner: AccountAddressInput,
    params: {
      feePercent?: number | bigint;
      feeWallet?: AccountAddressInput;
    }
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: owner,
      data: {
        function: `${this.packageId}::otc_pre_market::update_config`,
        functionArguments: [
          new MoveOption(params.feePercent ? new U64(params.feePercent) : 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}::otc_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}::otc_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}::otc_pre_market::remove_role`,
        functionArguments: [user],
      },
    };

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

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

  async buildForceCancelOffer(
    operator: AccountAddressInput,
    offerObj: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::otc_pre_market::force_cancel_offer`,
        functionArguments: [offerObj],
      },
    };

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

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

  async buildForceCancelOffers(
    operator: AccountAddressInput,
    offerObjs: AccountAddressInput[]
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: operator,
      data: {
        function: `${this.packageId}::otc_pre_market::force_cancel_offers`,
        functionArguments: [offerObjs.map((addr) => AccountAddress.from(addr))],
      },
    };

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

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

  async buildCreateOffer(
    user: AccountAddressInput,
    params: {
      orderObj: AccountAddressInput;
      isBuyer: boolean;
      exToken: AccountAddressInput;
      value: number | bigint;
      deadline: number | bigint;
    }
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: user,
      data: {
        function: `${this.packageId}::otc_pre_market::create_offer`,
        functionArguments: [
          AccountAddress.from(params.orderObj),
          params.isBuyer,
          AccountAddress.from(params.exToken),
          new U64(params.value),
          new U64(params.deadline),
        ],
      },
    };

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

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

  async buildBatchCreateOffer(
    user: AccountAddressInput,
    params: {
      orderObjs: AccountAddressInput[];
      isBuyers: boolean[];
      exTokens: AccountAddressInput[];
      values: (number | bigint)[];
      deadlines: (number | bigint)[];
    }
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: user,
      data: {
        function: `${this.packageId}::otc_pre_market::batch_create_offer`,
        functionArguments: [
          params.orderObjs.map((addr) => AccountAddress.from(addr)),
          params.isBuyers,
          params.exTokens.map((addr) => AccountAddress.from(addr)),
          params.values.map((v) => new U64(v)),
          params.deadlines.map((d) => new U64(d)),
        ],
      },
    };

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

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

  async buildUpdateOffer(
    user: AccountAddressInput,
    params: {
      offerObj: AccountAddressInput;
      newValue: number | bigint;
      newDeadline: number | bigint;
    }
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: user,
      data: {
        function: `${this.packageId}::otc_pre_market::update_offer`,
        functionArguments: [
          AccountAddress.from(params.offerObj),
          new U64(params.newValue),
          new U64(params.newDeadline),
        ],
      },
    };

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

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

  async buildFillOffer(
    user: AccountAddressInput,
    offerObj: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const otcOfferData = await this.getOtcOffer(offerObj);
    const { useCoin, coinType } = await checkCoinToFa(
      this.aptos,
      user,
      otcOfferData.exToken
    );
    let txData: InputGenerateTransactionData;

    if (!useCoin) {
      txData = {
        sender: user,
        data: {
          function: `${this.packageId}::otc_pre_market::fill_offer`,
          functionArguments: [AccountAddress.from(offerObj)],
        },
      };
    } else {
      txData = {
        sender: user,
        data: {
          function: `${this.packageId}::otc_pre_market::fill_offer_with_coin`,
          functionArguments: [AccountAddress.from(offerObj)],
          typeArguments: [coinType],
        },
      };
    }

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

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

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

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

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

  async buildCancelExpiredOffer(
    user: AccountAddressInput,
    offerObj: AccountAddressInput
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: user,
      data: {
        function: `${this.packageId}::otc_pre_market::cancel_expired_offer`,
        functionArguments: [AccountAddress.from(offerObj)],
      },
    };

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

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

  async buildCancelExpiredOffers(
    user: AccountAddressInput,
    offerObjs: AccountAddressInput[]
  ): Promise<{
    rawTx: AnyRawTransaction;
    txData: InputTransactionData;
  }> {
    const txData: InputGenerateTransactionData = {
      sender: user,
      data: {
        function: `${this.packageId}::otc_pre_market::cancel_expired_offers`,
        functionArguments: [offerObjs.map((addr) => AccountAddress.from(addr))],
      },
    };

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

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