import { TransactionResponse, TransactionReceipt } from "ethers";

import { HardhatRuntimeEnvironment } from "hardhat/types/hre";

import { Reporter } from "../reporters/Reporter.js";

import { networkManager } from "../network/NetworkManager.js";

import { CatchClassError } from "../../utils/index.js";

import { MigrateError } from "../../utils/index.js";

import type { MigrateConfig } from "../../../types/index.js";

@CatchClassError
class BaseTransactionRunner {
  protected _config: MigrateConfig;

  protected totalCost: bigint = 0n;
  protected totalTransactions: bigint = 0n;

  constructor(config: MigrateConfig) {
    this._config = config;
  }

  public async reportTransactionResponse(tx: TransactionResponse, instanceName: string) {
    // Switch the default provider to the Wrapped provider, incorporating network error handling
    Object.defineProperty(tx, "provider", {
      value: networkManager!.provider.provider,
      writable: true,
      configurable: true,
    });

    Reporter!.reportTransactionResponseHeader(tx, instanceName);

    let receipt;
    if (tx.isMined()) {
      receipt = (await tx.wait())!;
    } else {
      receipt = await this._showTransactionMining(tx);
    }

    await Reporter!.reportTransactionReceipt(receipt);

    this.totalCost += BigInt(receipt.fee ?? 0n) + BigInt(tx.value ?? 0n);
    this.totalTransactions++;
  }

  public summary() {
    Reporter!.summary(this.totalTransactions, this.totalCost);
  }

  protected async _showTransactionMining(tx: TransactionResponse) {
    await Reporter!.startTxReporting(tx);

    let receipt: TransactionReceipt;
    try {
      // We will wait for both contract deployment and common transactions
      receipt = (await tx.wait(this._config.execution.wait))!;
    } catch (e: any) {
      throw new MigrateError(`Transaction failed: ${e.message}`);
    } finally {
      Reporter!.stopSpinner();
    }

    return receipt;
  }
}

export let TransactionRunner: BaseTransactionRunner | null = null;

export function createTransactionRunner(hre: HardhatRuntimeEnvironment) {
  if (TransactionRunner) {
    return;
  }

  TransactionRunner = new BaseTransactionRunner(hre.config.migrate);
}

/**
 * Used only in test environments to ensure test atomicity
 */
export function resetTransactionRunner() {
  TransactionRunner = null;
}
