// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import aptosClient from "@aptos-labs/aptos-client";
import {
  AptosSettings,
  ClientConfig,
  Client,
  FullNodeConfig,
  IndexerConfig,
  FaucetConfig,
  TransactionGenerationConfig,
  PluginConfig,
  TransactionSubmitter,
} from "../types/index.js";
import {
  NetworkToNodeAPI,
  NetworkToFaucetAPI,
  NetworkToIndexerAPI,
  Network,
  NetworkToPepperAPI,
  NetworkToProverAPI,
} from "../utils/apiEndpoints.js";
import { AptosApiType, DEFAULT_MAX_GAS_AMOUNT, DEFAULT_TXN_EXP_SEC_FROM_NOW } from "../utils/const.js";
import { isBun } from "../utils/helpers.js";

/**
 * Represents the configuration settings for an Aptos SDK client instance.
 * This class allows customization of various endpoints and client settings.
 *
 * @example
 * ```typescript
 * import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
 *
 * async function runExample() {
 *     // Create a configuration for connecting to the Aptos testnet
 *     const config = new AptosConfig({ network: Network.TESTNET });
 *
 *     // Initialize the Aptos client with the configuration
 *     const aptos = new Aptos(config);
 *
 *     console.log("Aptos client initialized:", aptos);
 * }
 * runExample().catch(console.error);
 * ```
 * @group Client
 */
export class AptosConfig {
  /**
   * The Network that this SDK is associated with. Defaults to DEVNET
   * @group Client
   */
  readonly network: Network;

  /**
   * The client instance the SDK uses. Defaults to `@aptos-labs/aptos-client
   * @group Client
   */
  readonly client: Client;

  /**
   * The optional hardcoded fullnode URL to send requests to instead of using the network
   * @group Client
   */
  readonly fullnode?: string;

  /**
   * The optional hardcoded faucet URL to send requests to instead of using the network
   * @group Client
   */
  readonly faucet?: string;

  /**
   * The optional hardcoded pepper service URL to send requests to instead of using the network
   * @group Client
   */
  readonly pepper?: string;

  /**
   * The optional hardcoded prover service URL to send requests to instead of using the network
   * @group Client
   */
  readonly prover?: string;

  /**
   * The optional hardcoded indexer URL to send requests to instead of using the network
   * @group Client
   */
  readonly indexer?: string;

  /**
   * Optional client configurations
   * @group Client
   */
  readonly clientConfig?: ClientConfig;

  /**
   * Optional specific Fullnode configurations
   * @group Client
   */
  readonly fullnodeConfig?: FullNodeConfig;

  /**
   * Optional specific Indexer configurations
   * @group Client
   */
  readonly indexerConfig?: IndexerConfig;

  /**
   * Optional specific Faucet configurations
   * @group Client
   */
  readonly faucetConfig?: FaucetConfig;

  /**
   * Optional specific Transaction Generation configurations
   * @group Client
   */
  readonly transactionGenerationConfig?: TransactionGenerationConfig;

  /**
   * Optional plugin config to override client behavior.
   * @group Client
   */
  private pluginConfig?: PluginConfig;

  /**
   * Whether reads of pruned history are retried against the archival endpoint the node advertises in its
   * `410 Gone` response. Defaults to `true`.
   *
   * The retry happens once, only for requests that would otherwise throw. Credentials are sent to the
   * archival endpoint only when it is on the same site as the node, since the node chooses that URL.
   *
   * @group Client
   */
  readonly archivalFallback: boolean;

  /**
   * Initializes an instance of the Aptos client with the specified settings.
   * This allows users to configure various aspects of the client, such as network and endpoints.
   *
   * @param settings - Optional configuration settings for the Aptos client.
   * @param settings.network - The network to connect to, defaults to `Network.DEVNET`.
   * @param settings.fullnode - The fullnode endpoint to use for requests.
   * @param settings.faucet - The faucet endpoint for obtaining test tokens.
   * @param settings.pepper - The pepper used for transaction signing.
   * @param settings.prover - The prover endpoint for transaction verification.
   * @param settings.indexer - The indexer endpoint for querying blockchain data.
   * @param settings.client - Custom client settings, defaults to a standard Aptos client.
   * @param settings.clientConfig - Additional configuration for the client.
   * @param settings.fullnodeConfig - Additional configuration for the fullnode.
   * @param settings.indexerConfig - Additional configuration for the indexer.
   * @param settings.faucetConfig - Additional configuration for the faucet.
   * @param settings.archivalFallback - Whether to retry pruned reads against the node's archival endpoint,
   * defaults to `true`.
   *
   * @example
   * ```typescript
   * import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
   *
   * async function runExample() {
   *     // Create a new Aptos client with default settings
   *     const config = new AptosConfig({ network: Network.TESTNET }); // Specify the network
   *     const aptos = new Aptos(config);
   *
   *     console.log("Aptos client initialized:", aptos);
   * }
   * runExample().catch(console.error);
   * ```
   * @group Client
   */
  constructor(settings?: AptosSettings) {
    // If there are any endpoint overrides, they are custom networks, keep that in mind
    if (settings?.fullnode || settings?.indexer || settings?.faucet || settings?.pepper || settings?.prover) {
      if (settings?.network === Network.CUSTOM) {
        console.info("Note: using CUSTOM network will require queries to lookup ChainId");
      } else if (!settings?.network) {
        throw new Error("Custom endpoints require a network to be specified");
      }
    }

    // Warn Bun users about HTTP/2 compatibility issues.
    // Bun's HTTP/2 support is not fully mature yet, so we recommend disabling it.
    if (isBun() && settings?.clientConfig?.http2 !== false) {
      console.warn(
        "[Aptos SDK] Bun does not fully support HTTP/2, which is enabled by default in this SDK. " +
          "It is recommended to disable HTTP/2 by setting `clientConfig: { http2: false }` in your AptosConfig. " +
          "For example: new AptosConfig({ network: Network.TESTNET, clientConfig: { http2: false } })",
      );
    }

    this.network = settings?.network ?? Network.DEVNET;
    this.fullnode = settings?.fullnode;
    this.faucet = settings?.faucet;
    this.pepper = settings?.pepper;
    this.prover = settings?.prover;
    this.indexer = settings?.indexer;
    // Type assertion needed: @aptos-labs/aptos-client's types don't resolve
    // correctly under nodenext module resolution
    this.client = settings?.client ?? { provider: aptosClient };
    this.clientConfig = settings?.clientConfig ?? {};
    this.fullnodeConfig = settings?.fullnodeConfig ?? {};
    this.indexerConfig = settings?.indexerConfig ?? {};
    this.faucetConfig = settings?.faucetConfig ?? {};
    this.transactionGenerationConfig = settings?.transactionGenerationConfig ?? {};
    this.archivalFallback = settings?.archivalFallback ?? true;
    this.pluginConfig = settings?.pluginSettings
      ? {
          ...settings.pluginSettings,
          IGNORE_TRANSACTION_SUBMITTER: false,
        }
      : undefined;
  }

  /**
   * Returns the URL endpoint to send the request to based on the specified API type.
   * If a custom URL was provided in the configuration, that URL is returned. Otherwise, the URL endpoint is derived from the network.
   *
   * @param apiType - The type of Aptos API to get the URL for. This can be one of the following: FULLNODE, FAUCET, INDEXER, PEPPER, PROVER.
   *
   * @example
   * ```typescript
   * import { Aptos, AptosConfig, Network, AptosApiType } from "@aptos-labs/ts-sdk";
   *
   * const config = new AptosConfig({ network: Network.TESTNET });
   * const aptos = new Aptos(config);
   *
   * async function runExample() {
   *   // Getting the request URL for the FULLNODE API
   *   const url = config.getRequestUrl(AptosApiType.FULLNODE);
   *   console.log("Request URL for FULLNODE:", url);
   * }
   * runExample().catch(console.error);
   * ```
   * @group Client
   */
  getRequestUrl(apiType: AptosApiType): string {
    switch (apiType) {
      case AptosApiType.FULLNODE:
        if (this.fullnode !== undefined) return this.fullnode;
        if (this.network === Network.CUSTOM) throw new Error("Please provide a custom full node url");
        return NetworkToNodeAPI[this.network];
      case AptosApiType.FAUCET:
        if (this.faucet !== undefined) return this.faucet;
        if (this.network === Network.TESTNET) {
          throw new Error(
            "There is no way to programmatically mint testnet APT, you must use the minting site at https://aptos.dev/network/faucet",
          );
        }
        if (this.network === Network.MAINNET) {
          throw new Error("There is no mainnet faucet");
        }
        if (this.network === Network.CUSTOM) throw new Error("Please provide a custom faucet url");
        return NetworkToFaucetAPI[this.network];
      case AptosApiType.INDEXER:
        if (this.indexer !== undefined) return this.indexer;
        if (this.network === Network.CUSTOM) throw new Error("Please provide a custom indexer url");
        return NetworkToIndexerAPI[this.network];
      case AptosApiType.PEPPER:
        if (this.pepper !== undefined) return this.pepper;
        if (this.network === Network.CUSTOM) throw new Error("Please provide a custom pepper service url");
        return NetworkToPepperAPI[this.network];
      case AptosApiType.PROVER:
        if (this.prover !== undefined) return this.prover;
        if (this.network === Network.CUSTOM) throw new Error("Please provide a custom prover service url");
        return NetworkToProverAPI[this.network];
      default:
        throw new Error(`apiType ${apiType} is not supported`);
    }
  }

  /**
   * Checks if the provided URL is a known pepper service endpoint.
   *
   * @param url - The URL to check against the known pepper service endpoints.
   *
   * @example
   * ```typescript
   * import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
   *
   * const config = new AptosConfig({ network: Network.TESTNET });
   * const aptos = new Aptos(config);
   *
   * async function runExample() {
   *     const url = "https://example.pepper.service"; // replace with a real pepper service URL
   *
   *     // Check if the URL is a known pepper service endpoint
   *     const isPepperService = config.isPepperServiceRequest(url);
   *
   *     console.log(`Is the URL a known pepper service? ${isPepperService}`);
   * }
   * runExample().catch(console.error);
   * ```
   * @group Client
   */
  isPepperServiceRequest(url: string): boolean {
    return NetworkToPepperAPI[this.network] === url;
  }

  /**
   * Checks if the provided URL is a known prover service endpoint.
   *
   * @param url - The URL to check against known prover service endpoints.
   * @returns A boolean indicating whether the URL is a known prover service endpoint.
   *
   * @example
   * ```typescript
   * import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
   *
   * const config = new AptosConfig({ network: Network.TESTNET });
   * const aptos = new Aptos(config);
   *
   * // Check if the URL is a known prover service endpoint
   * const url = "https://prover.testnet.aptos.dev"; // replace with a real URL if needed
   * const isProver = config.isProverServiceRequest(url);
   *
   * console.log(`Is the URL a known prover service? ${isProver}`);
   * ```
   * @group Client
   */
  isProverServiceRequest(url: string): boolean {
    return NetworkToProverAPI[this.network] === url;
  }

  getDefaultMaxGasAmount(): number {
    return this.transactionGenerationConfig?.defaultMaxGasAmount ?? DEFAULT_MAX_GAS_AMOUNT;
  }

  getDefaultTxnExpirySecFromNow(): number {
    return this.transactionGenerationConfig?.defaultTxnExpirySecFromNow ?? DEFAULT_TXN_EXP_SEC_FROM_NOW;
  }

  /**
   * If you have set a custom transaction submitter, you can use this to determine
   * whether to use it or not. For example, to stop using the transaction submitter:
   *
   * @example
   * ```
   * aptos.config.setIgnoreTransactionSubmitter(true);
   * ```
   *
   * @group Client
   */
  setIgnoreTransactionSubmitter(ignore: boolean) {
    if (this.pluginConfig) {
      this.pluginConfig.IGNORE_TRANSACTION_SUBMITTER = ignore;
    }
  }

  /**
   * If a custom transaction submitter has been specified in the PluginConfig and
   * IGNORE_TRANSACTION_SUBMITTER is false, this will return a transaction submitter
   * that should be used instead of the default transaction submission behavior.
   */
  getTransactionSubmitter(): TransactionSubmitter | undefined {
    if (this.pluginConfig === undefined) {
      return undefined;
    }

    if (this.pluginConfig.IGNORE_TRANSACTION_SUBMITTER === true) {
      return undefined;
    }

    return this.pluginConfig.TRANSACTION_SUBMITTER;
  }
}
