import type { BlockInfo } from "@ledgerhq/coin-module-framework/api/types";
import { getEnv } from "@ledgerhq/live-env";
import { BigNumber } from "bignumber.js";
import { HEDERA_TRANSACTION_NAMES, FINALITY_MS } from "../constants";
import { apiClient } from "../network/api";
import { hgraphClient } from "../network/hgraph";
import { enrichERC20Transfers } from "../network/utils";
import { getMockedEnrichedERC20Transfer } from "../test/fixtures/common.fixture";
import { getMockedERC20TokenCurrency } from "../test/fixtures/currency.fixture";
import { getMockedERC20TokenTransfer } from "../test/fixtures/hgraph.fixture";
import {
  getMockedMirrorAccount,
  getMockedMirrorContractCallResult,
  getMockedMirrorTransaction,
} from "../test/fixtures/mirror.fixture";
import type { StakingAnalysis } from "../types";
import { getBlockV2 } from "./getBlock.v2";
import { getBlockInfo } from "./getBlockInfo";
import { analyzeStakingOperation, getDateRangeFromBlockHeight } from "./utils";

jest.mock("./getBlockInfo");
jest.mock("../network/api");
jest.mock("../network/hgraph");
jest.mock("../network/utils", () => ({
  ...jest.requireActual("../network/utils"),
  enrichERC20Transfers: jest.fn(),
}));
jest.mock("./utils", () => ({
  ...jest.requireActual("./utils"),
  getDateRangeFromBlockHeight: jest.fn(),
  analyzeStakingOperation: jest.fn().mockResolvedValue(null),
  fromEVMAddress: jest.fn().mockImplementation((evmAddress: string) => {
    const addressMap: Record<string, string> = {
      "0x0000000000000000000000000000000000000999": "0.0.999",
      "0x0000000000000000000000000000000000001001": "0.0.1001",
    };
    return addressMap[evmAddress] || null;
  }),
}));

describe("getBlockV2", () => {
  const mockBlockInfo: BlockInfo = {
    height: 100,
    hash: "mock_hash",
    time: new Date("2024-01-01T00:00:00Z"),
  };

  const mockDateRange = {
    start: new Date(1704067200123),
    end: new Date(1704067260456),
  };

  beforeEach(() => {
    jest.clearAllMocks();

    (getBlockInfo as jest.Mock).mockResolvedValue(mockBlockInfo);
    (getDateRangeFromBlockHeight as jest.Mock).mockReturnValue(mockDateRange);
    (analyzeStakingOperation as jest.Mock).mockResolvedValue(null);
    (enrichERC20Transfers as jest.Mock).mockReturnValue([]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber("1704067200123000000000"),
    );
  });

  it("should return empty block when no transactions exist", async () => {
    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([]);

    const result = await getBlockV2(100);

    expect(result).toEqual({
      info: mockBlockInfo,
      transactions: [],
    });
  });

  it("should call dependencies with correct parameters", async () => {
    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([]);

    await getBlockV2(42);

    expect(getDateRangeFromBlockHeight).toHaveBeenCalledWith(42);
    expect(getBlockInfo).toHaveBeenCalledWith(42);
    expect(apiClient.getTransactionsByTimestampRange).toHaveBeenCalledTimes(1);
    expect(apiClient.getTransactionsByTimestampRange).toHaveBeenCalledWith({
      startTimestamp: `gte:1704067200.123`,
      endTimestamp: `lt:1704067260.456`,
      limit: 100,
      order: "desc",
    });
    expect(hgraphClient.getERC20TransfersByTimestampRange).toHaveBeenCalledTimes(1);
    expect(hgraphClient.getERC20TransfersByTimestampRange).toHaveBeenCalledWith({
      startTimestamp: "1704067200.123000000",
      endTimestamp: "1704067260.456000000",
      limit: 100,
      order: "desc",
    });
    expect(enrichERC20Transfers).toHaveBeenCalledTimes(1);
    expect(hgraphClient.getLatestIndexedConsensusTimestamp).toHaveBeenCalledTimes(1);
  });

  it("should extract fee payer from transaction_id by default", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash",
      name: "CRYPTOTRANSFER",
      result: "SUCCESS",
      charged_tx_fee: 100000,
      staking_reward_transfers: [],
      transfers: [],
      token_transfers: [],
    });

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);

    const result = await getBlockV2(100);

    expect(result.transactions[0].feesPayer).toBe("0.0.999");
  });

  it("should infer fee payer from transfers when initiator is not debited", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.10067173-1761755118-730000493",
      transaction_hash: "hash",
      name: "CRYPTOTRANSFER",
      result: "INSUFFICIENT_PAYER_BALANCE",
      charged_tx_fee: 40743,
      staking_reward_transfers: [],
      transfers: [
        { account: "0.0.23", amount: -40743 },
        { account: "0.0.801", amount: 40743 },
      ],
      token_transfers: [],
    });

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);

    const result = await getBlockV2(100);
    const payerOperation = result.transactions[0].operations.find(op => op.address === "0.0.23");

    expect(result.transactions[0].feesPayer).toBe("0.0.23");
    expect(payerOperation).toMatchObject({
      address: "0.0.23",
      amount: BigInt(0),
    });
  });

  it("should exclude fee from payer's operation amount", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash",
      name: "CRYPTOTRANSFER",
      result: "SUCCESS",
      charged_tx_fee: 67179,
      staking_reward_transfers: [],
      transfers: [
        {
          account: "0.0.999",
          amount: -567179,
        },
        {
          account: "0.0.1001",
          amount: 500000,
        },
      ],
      token_transfers: [],
    });

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);

    const result = await getBlockV2(100);
    const senderOperation = result.transactions[0].operations.find(op => op.address === "0.0.999");

    expect(senderOperation).toMatchObject({
      address: "0.0.999",
      amount: BigInt(-567179 + 67179),
    });
  });

  it("should handle HTS token transfers", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash",
      name: "CRYPTOTRANSFER",
      result: "SUCCESS",
      charged_tx_fee: 100000,
      staking_reward_transfers: [],
      transfers: [],
      token_transfers: [
        {
          token_id: "0.0.12345",
          account: "0.0.999",
          amount: -1000,
        },
        {
          token_id: "0.0.12345",
          account: "0.0.1001",
          amount: 1000,
        },
      ],
    });

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);

    const result = await getBlockV2(100);

    expect(result.transactions[0].operations).toEqual([
      {
        type: "transfer",
        address: "0.0.999",
        asset: {
          type: "hts",
          assetReference: "0.0.12345",
        },
        amount: BigInt(-1000),
      },
      {
        type: "transfer",
        address: "0.0.1001",
        asset: {
          type: "hts",
          assetReference: "0.0.12345",
        },
        amount: BigInt(1000),
      },
    ]);
  });

  it("should handle ERC20 token transfers", async () => {
    const mockMirrorAccount = getMockedMirrorAccount({ account: "0.0.12345" });
    const mockTokenERC20 = getMockedERC20TokenCurrency();

    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "erc20-transfer-hash",
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      memo_base64: "xyz",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });

    const mockERC20Transfer = getMockedERC20TokenTransfer({
      token_evm_address: mockTokenERC20.contractAddress,
      transaction_hash: mockMirrorTransaction.transaction_hash,
      consensus_timestamp:
        Number(mockMirrorTransaction.consensus_timestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: 12345,
      receiver_account_id: 67890,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });

    const mockContractCallResult = getMockedMirrorContractCallResult({
      block_hash: "0xblockhash123",
      gas_consumed: 75000,
      gas_limit: 100000,
      gas_used: 75000,
    });

    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([
      mockMirrorTransaction,
    ]);
    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([
      mockERC20Transfer,
    ]);
    (enrichERC20Transfers as jest.Mock).mockReturnValue([mockEnrichedERC20Transfer]);

    const result = await getBlockV2(100);

    expect(result.transactions[0].operations).toEqual([
      {
        type: "transfer",
        address: "0.0.12345",
        asset: {
          type: "native",
        },
        amount: BigInt(0),
      },
      {
        type: "transfer",
        address: "0.0.67890",
        asset: {
          type: "erc20",
          assetReference: mockTokenERC20.contractAddress,
        },
        amount: BigInt(mockERC20Transfer.amount),
      },
      {
        type: "transfer",
        address: "0.0.12345",
        asset: {
          type: "erc20",
          assetReference: mockTokenERC20.contractAddress,
        },
        amount: BigInt(-mockERC20Transfer.amount),
      },
    ]);
  });

  it("should deduplicate CONTRACT_CALL when matching ERC20 transfer exists", async () => {
    const mockMirrorAccount = getMockedMirrorAccount({ account: "0.0.12345" });
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const sharedHash = "duplicate-hash";
    const sharedTimestamp = "1625097600.000000000";

    const mockMirrorContractCall = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      transaction_id: "0.0.12345-1625097600-000000000",
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });

    const mockERC20Transfer = getMockedERC20TokenTransfer({
      token_evm_address: mockTokenERC20.contractAddress,
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: 12345,
      receiver_account_id: 67890,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });

    const mockContractCallResult = getMockedMirrorContractCallResult();

    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorContractCall,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([
      mockMirrorContractCall,
    ]);
    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([
      mockERC20Transfer,
    ]);
    (enrichERC20Transfers as jest.Mock).mockReturnValue([mockEnrichedERC20Transfer]);

    const result = await getBlockV2(100);

    expect(result.transactions).toEqual([expect.objectContaining({ hash: sharedHash })]);
  });

  it("should handle ERC20 transfer with null account_ids (using EVM addresses only)", async () => {
    const mockMirrorAccount = getMockedMirrorAccount({ account: "0.0.12345" });
    const mockTokenERC20 = getMockedERC20TokenCurrency();

    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "erc20-transfer-hash",
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });

    const mockERC20Transfer = getMockedERC20TokenTransfer({
      token_evm_address: mockTokenERC20.contractAddress,
      transaction_hash: mockMirrorTransaction.transaction_hash,
      consensus_timestamp:
        Number(mockMirrorTransaction.consensus_timestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: null,
      receiver_account_id: null,
      sender_evm_address: "0x0000000000000000000000000000000000000999",
      receiver_evm_address: "0x0000000000000000000000000000000000001001",
      payer_account_id: 12345,
      amount: 5000000,
    });

    const mockContractCallResult = getMockedMirrorContractCallResult();

    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([
      mockMirrorTransaction,
    ]);
    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([
      mockERC20Transfer,
    ]);
    (enrichERC20Transfers as jest.Mock).mockReturnValue([mockEnrichedERC20Transfer]);

    const result = await getBlockV2(100);

    expect(result.transactions).toHaveLength(1);
    expect(result.transactions[0].operations).toEqual([
      {
        address: mockMirrorAccount.account,
        amount: BigInt(0),
        asset: {
          type: "native",
        },
        type: "transfer",
      },
      {
        type: "transfer",
        address: mockERC20Transfer.receiver_evm_address,
        asset: {
          type: "erc20",
          assetReference: mockTokenERC20.contractAddress,
        },
        amount: BigInt(mockERC20Transfer.amount),
      },
      {
        type: "transfer",
        address: mockERC20Transfer.sender_evm_address,
        asset: {
          type: "erc20",
          assetReference: mockTokenERC20.contractAddress,
        },
        amount: BigInt(-mockERC20Transfer.amount),
      },
    ]);
  });

  it.each([
    {
      label: "sender evm address is null",
      transferOverrides: { sender_account_id: null, sender_evm_address: null },
    },
    {
      label: "recipient evm address is null",
      transferOverrides: { receiver_account_id: null, receiver_evm_address: null },
    },
  ])("should skip ERC20 operations when $label", async ({ transferOverrides }) => {
    const mockMirrorAccount = getMockedMirrorAccount({ account: "0.0.12345" });
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "erc20-transfer-hash",
      result: "SUCCESS",
      name: "CONTRACTCALL",
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      token_evm_address: mockTokenERC20.contractAddress,
      transaction_hash: mockMirrorTransaction.transaction_hash,
      ...transferOverrides,
    });

    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([
      mockMirrorTransaction,
    ]);
    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([
      mockERC20Transfer,
    ]);
    (enrichERC20Transfers as jest.Mock).mockReturnValue([mockEnrichedERC20Transfer]);

    const result = await getBlockV2(100);
    const operations = result.transactions[0]?.operations;
    const erc20Operations = operations?.filter(
      op => op.type === "transfer" && op.asset.type === "erc20",
    );

    expect(erc20Operations).toEqual([]);
  });

  it("should mark failed transactions", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash",
      name: "CRYPTOTRANSFER",
      result: "INSUFFICIENT_ACCOUNT_BALANCE",
      charged_tx_fee: 100000,
      staking_reward_transfers: [],
      transfers: [],
      token_transfers: [],
    });

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);

    const result = await getBlockV2(100);

    expect(result.transactions[0].failed).toBe(true);
  });

  it("should analyze CRYPTOUPDATEACCOUNT transactions for staking", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash_update",
      name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
      result: "SUCCESS",
      charged_tx_fee: 22000,
      consensus_timestamp: "1704067210.123456789",
      staking_reward_transfers: [],
      transfers: [],
      token_transfers: [],
    });
    const mockStakingAnalysis: StakingAnalysis = {
      operationType: "DELEGATE",
      targetStakingNodeId: 5,
      previousStakingNodeId: null,
      stakedAmount: BigInt(100),
    };

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
    (analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);

    const result = await getBlockV2(100);

    expect(analyzeStakingOperation).toHaveBeenCalledTimes(1);
    expect(analyzeStakingOperation).toHaveBeenCalledWith("0.0.999", mockTx);
    expect(result.transactions[0].operations).toHaveLength(1);
    expect(result.transactions[0].operations[0]).toEqual({
      type: "other",
      operationType: mockStakingAnalysis.operationType,
      stakedNodeId: mockStakingAnalysis.targetStakingNodeId,
      previousStakedNodeId: mockStakingAnalysis.previousStakingNodeId,
      stakedAmount: mockStakingAnalysis.stakedAmount,
    });
  });

  it("should handle UNDELEGATE staking operation", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash_undelegate",
      name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
      result: "SUCCESS",
      charged_tx_fee: 22000,
      consensus_timestamp: "1704067210.123456789",
      staking_reward_transfers: [],
      transfers: [],
      token_transfers: [],
    });
    const mockStakingAnalysis: StakingAnalysis = {
      operationType: "UNDELEGATE",
      targetStakingNodeId: null,
      previousStakingNodeId: 3,
      stakedAmount: BigInt(100),
    };

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
    (analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);

    const result = await getBlockV2(100);

    expect(result.transactions[0].operations[0]).toEqual({
      type: "other",
      operationType: mockStakingAnalysis.operationType,
      stakedNodeId: mockStakingAnalysis.targetStakingNodeId,
      previousStakedNodeId: mockStakingAnalysis.previousStakingNodeId,
      stakedAmount: mockStakingAnalysis.stakedAmount,
    });
  });

  it("should handle REDELEGATE staking operation", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash_redelegate",
      name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
      result: "SUCCESS",
      charged_tx_fee: 22000,
      consensus_timestamp: "1704067210.123456789",
      staking_reward_transfers: [],
      transfers: [],
      token_transfers: [],
    });
    const mockStakingAnalysis: StakingAnalysis = {
      operationType: "REDELEGATE",
      targetStakingNodeId: 10,
      previousStakingNodeId: 5,
      stakedAmount: BigInt(100),
    };

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
    (analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);

    const result = await getBlockV2(100);

    expect(result.transactions[0].operations).toEqual([
      {
        type: "other",
        operationType: mockStakingAnalysis.operationType,
        stakedNodeId: mockStakingAnalysis.targetStakingNodeId,
        previousStakedNodeId: mockStakingAnalysis.previousStakingNodeId,
        stakedAmount: mockStakingAnalysis.stakedAmount,
      },
    ]);
  });

  it("should create CLAIM_REWARDS operations for staking reward transfers", async () => {
    const account1 = "0.0.999";
    const account2 = "0.0.1001";
    const stakingRewardAccount = getEnv("HEDERA_STAKING_REWARD_ACCOUNT_ID");
    const rewardAccount1 = 30313674;
    const rewardAccount2 = 191772;
    const chargedFee = 79874;

    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash",
      name: "CRYPTOTRANSFER",
      result: "SUCCESS",
      charged_tx_fee: chargedFee,
      staking_reward_transfers: [
        {
          account: account1,
          amount: rewardAccount1,
        },
        {
          account: account2,
          amount: rewardAccount2,
        },
      ],
      transfers: [
        {
          account: "0.0.35",
          amount: 3235,
        },
        {
          account: stakingRewardAccount,
          amount: -30505446,
        },
        {
          account: "0.0.801",
          amount: 76639,
        },
        {
          account: account1,
          amount: 29233800,
        },
        {
          account: account2,
          amount: 1191772,
        },
      ],
      token_transfers: [],
    });

    const totalRewards = mockTx.staking_reward_transfers.reduce((acc, t) => acc + t.amount, 0);

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);

    const result = await getBlockV2(100);

    expect(result.transactions[0].operations).toEqual([
      {
        type: "transfer",
        address: "0.0.35",
        asset: { type: "native" },
        amount: BigInt(3235),
      },
      {
        type: "transfer",
        address: stakingRewardAccount,
        asset: { type: "native" },
        amount: BigInt(-totalRewards),
      },
      {
        type: "transfer",
        address: "0.0.801",
        asset: { type: "native" },
        amount: BigInt(76639),
      },
      {
        type: "transfer",
        address: account1,
        asset: { type: "native" },
        amount: BigInt(29233800 + chargedFee - rewardAccount1),
      },
      {
        type: "transfer",
        address: account2,
        asset: { type: "native" },
        amount: BigInt(1191772 - rewardAccount2),
      },
      {
        type: "transfer",
        address: account1,
        asset: { type: "native" },
        amount: BigInt(rewardAccount1),
      },
      {
        type: "transfer",
        address: account2,
        asset: { type: "native" },
        amount: BigInt(rewardAccount2),
      },
    ]);
  });

  it("should handle CRYPTOUPDATEACCOUNT if it's not related to staking", async () => {
    const mockTx = getMockedMirrorTransaction({
      transaction_id: "0.0.999-1234567890-000000000",
      transaction_hash: "hash_regular_update",
      name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
      result: "SUCCESS",
      charged_tx_fee: 22000,
      consensus_timestamp: "1704067210.123456789",
      staking_reward_transfers: [],
      transfers: [
        {
          account: "0.0.999",
          amount: -23000,
        },
        {
          account: "0.0.1000",
          amount: 1000,
        },
      ],
      token_transfers: [],
    });

    (hgraphClient.getERC20TransfersByTimestampRange as jest.Mock).mockResolvedValue([]);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
    (analyzeStakingOperation as jest.Mock).mockResolvedValue(null);

    const result = await getBlockV2(100);

    expect(result.transactions[0].operations).toEqual([
      {
        type: "transfer",
        address: "0.0.999",
        asset: { type: "native" },
        amount: BigInt(-1000),
      },
      {
        type: "transfer",
        address: "0.0.1000",
        asset: { type: "native" },
        amount: BigInt(1000),
      },
    ]);
  });

  it("should throw error when querying a block in the future", async () => {
    const now = Date.now();
    const futureRange = {
      start: new Date(now + 60_000),
      end: new Date(now + 120_000),
    };

    (getDateRangeFromBlockHeight as jest.Mock).mockReturnValue(futureRange);

    await expect(getBlockV2(999)).rejects.toThrow("Block 999 is not available yet");
    expect(getBlockInfo).not.toHaveBeenCalled();
    expect(apiClient.getTransactionsByTimestampRange).not.toHaveBeenCalled();
  });

  it("should throw error when querying a block overlapping the non-finalized window", async () => {
    const now = Date.now();
    const overlappingRange = {
      start: new Date(now - FINALITY_MS - 1_000),
      end: new Date(now - FINALITY_MS / 2),
    };

    (getDateRangeFromBlockHeight as jest.Mock).mockReturnValue(overlappingRange);

    await expect(getBlockV2(998)).rejects.toThrow("Block 998 is not available yet");
    expect(getBlockInfo).not.toHaveBeenCalled();
    expect(apiClient.getTransactionsByTimestampRange).not.toHaveBeenCalled();
  });

  it("should succeed when querying the finalized window", async () => {
    const now = Date.now();
    const finalizedRange = {
      start: new Date(now - FINALITY_MS - 120_000),
      end: new Date(now - FINALITY_MS - 60_000),
    };

    (getDateRangeFromBlockHeight as jest.Mock).mockReturnValue(finalizedRange);
    (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([]);

    const result = await getBlockV2(100);

    expect(result).toEqual({
      info: mockBlockInfo,
      transactions: [],
    });
    expect(getBlockInfo).toHaveBeenCalledWith(100);
    expect(apiClient.getTransactionsByTimestampRange).toHaveBeenCalled();
  });

  it("should throw error when hgraph is not synced up to the end of the block range", async () => {
    const blockHeight = 100;
    // 1 ns behind the end of the block's date range
    const endTimestampNs = new BigNumber(mockDateRange.end.getTime())
      .multipliedBy(10 ** 6)
      .minus(1);

    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      endTimestampNs,
    );

    await expect(getBlockV2(blockHeight)).rejects.toThrow(
      `Block ${blockHeight} has no ERC20 synced yet (${endTimestampNs})`,
    );
    expect(getBlockInfo).not.toHaveBeenCalled();
    expect(apiClient.getTransactionsByTimestampRange).not.toHaveBeenCalled();
  });
});
