/**
 * @jest-environment jsdom
 */
/* eslint-disable @typescript-eslint/consistent-type-assertions */
import { renderHook } from "@testing-library/react";
import { BigNumber } from "bignumber.js";
import { createFixtureAccount } from "../../mock/fixtures/cryptoCurrencies";
import type { Transaction, TransactionStatus } from "../../generated/types";
import { useBitcoinUtxoDisplayData, type UseBitcoinUtxoDisplayDataParams } from "./react";
import type { BitcoinAccount, BitcoinOutput } from "./types";
import { bitcoinPickingStrategy } from "./types";

jest.mock("./logic", () => ({
  getUTXOStatus: jest.fn(),
}));

jest.mock("./logic", () => ({
  getUTXOStatus: jest.fn(),
}));

jest.mock("./logic", () => ({
  getUTXOStatus: jest.fn(),
}));

jest.mock("@ledgerhq/coin-framework/currencies/formatCurrencyUnit", () => ({
  formatCurrencyUnit: jest.fn((_unit: unknown, value: BigNumber) => `${value.toString()} BTC`),
}));

const getUTXOStatus = jest.requireMock("./logic").getUTXOStatus;

function createBitcoinAccount(overrides?: Partial<BitcoinAccount>): BitcoinAccount {
  return {
    ...createFixtureAccount("bitcoin"),
    ...overrides,
  } as BitcoinAccount;
}

function createUtxo(overrides?: Partial<BitcoinOutput>): BitcoinOutput {
  return {
    hash: "abc123",
    outputIndex: 0,
    blockHeight: 799000,
    address: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
    value: new BigNumber(10000),
    rbf: false,
    isChange: false,
    ...overrides,
  };
}

function createBitcoinTransaction(overrides?: Partial<Transaction>): Transaction {
  return {
    family: "bitcoin",
    amount: new BigNumber(0),
    recipient: "",
    useAllAmount: false,
    utxoStrategy: {
      strategy: bitcoinPickingStrategy.MERGE_OUTPUTS,
      excludeUTXOs: [],
    },
    ...overrides,
  } as Transaction;
}

function createBitcoinStatus(overrides?: Partial<TransactionStatus>): TransactionStatus {
  return {
    errors: {},
    warnings: {},
    estimatedFees: new BigNumber(0),
    amount: new BigNumber(0),
    totalSpent: new BigNumber(5000),
    txInputs: [],
    ...overrides,
  } as TransactionStatus;
}

describe("useBitcoinUtxoDisplayData", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    (getUTXOStatus as jest.Mock).mockReturnValue({ excluded: false });
  });

  it("should return null when account is not Bitcoin-based", () => {
    const account = createFixtureAccount("ethereum");
    const params: UseBitcoinUtxoDisplayDataParams = {
      account,
      transaction: createBitcoinTransaction(),
      status: createBitcoinStatus(),
      locale: "en",
    };

    const { result } = renderHook(() => useBitcoinUtxoDisplayData(params));

    expect(result.current).toBeNull();
    expect(getUTXOStatus).not.toHaveBeenCalled();
  });

  it("should return null when account has no bitcoinResources", () => {
    const account = createFixtureAccount("bitcoin");
    const params: UseBitcoinUtxoDisplayDataParams = {
      account,
      transaction: createBitcoinTransaction(),
      status: createBitcoinStatus(),
      locale: "en",
    };

    const { result } = renderHook(() => useBitcoinUtxoDisplayData(params));

    expect(result.current).toBeNull();
  });

  it("should return null when transaction has no utxoStrategy", () => {
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [createUtxo()] },
    });
    const transactionWithoutStrategy = {
      ...createBitcoinTransaction(),
      utxoStrategy: undefined,
    };
    const params: UseBitcoinUtxoDisplayDataParams = {
      account,
      transaction: transactionWithoutStrategy as Transaction,
      status: createBitcoinStatus(),
      locale: "en",
    };

    const { result } = renderHook(() => useBitcoinUtxoDisplayData(params));

    expect(result.current).toBeNull();
  });

  it("should return null when status has no txInputs (not Bitcoin transaction status)", () => {
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [createUtxo()] },
    });
    const statusWithoutTxInputs = createBitcoinStatus();
    delete (statusWithoutTxInputs as Record<string, unknown>).txInputs;
    const params: UseBitcoinUtxoDisplayDataParams = {
      account,
      transaction: createBitcoinTransaction(),
      status: statusWithoutTxInputs,
      locale: "en",
    };

    const { result } = renderHook(() => useBitcoinUtxoDisplayData(params));

    expect(result.current).toBeNull();
  });

  it("should return null when bitcoinResources has no utxos", () => {
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [] },
    });
    const params: UseBitcoinUtxoDisplayDataParams = {
      account,
      transaction: createBitcoinTransaction(),
      status: createBitcoinStatus(),
      locale: "en",
    };

    const { result } = renderHook(() => useBitcoinUtxoDisplayData(params));

    expect(result.current).toBeNull();
  });

  it("should return display data with correct shape when all conditions are met", () => {
    const utxo = createUtxo();
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [utxo] },
      blockHeight: 800000,
    });
    const transaction = createBitcoinTransaction();
    const status = createBitcoinStatus({ totalSpent: new BigNumber(3000) });
    const params: UseBitcoinUtxoDisplayDataParams = {
      account,
      transaction,
      status,
      locale: "en",
    };

    const { result } = renderHook(() => useBitcoinUtxoDisplayData(params));

    expect(result.current).not.toBeNull();
    expect(result.current).toMatchObject({
      pickingStrategyValue: bitcoinPickingStrategy.MERGE_OUTPUTS,
      totalExcludedUTXOS: 0,
      totalSpent: new BigNumber(3000),
    });
    expect(result.current?.pickingStrategyOptions).toHaveLength(
      Object.keys(bitcoinPickingStrategy).length,
    );
    expect(result.current?.utxoRows).toHaveLength(1);
    expect(getUTXOStatus).toHaveBeenCalledWith(
      utxo,
      (transaction as { utxoStrategy: unknown }).utxoStrategy,
    );
  });

  it("should compute totalExcludedUTXOS from getUTXOStatus results", () => {
    const utxo1 = createUtxo({ outputIndex: 0 });
    const utxo2 = createUtxo({ hash: "def456", outputIndex: 1 });
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [utxo1, utxo2] },
    });
    (getUTXOStatus as jest.Mock).mockImplementation((u: BitcoinOutput) => ({
      excluded: u.outputIndex === 1,
      reason: u.outputIndex === 1 ? ("userExclusion" as const) : undefined,
    }));

    const { result } = renderHook(() =>
      useBitcoinUtxoDisplayData({
        account,
        transaction: createBitcoinTransaction(),
        status: createBitcoinStatus(),
        locale: "en",
      }),
    );

    expect(result.current).not.toBeNull();
    expect(result.current?.totalExcludedUTXOS).toBe(1);
    expect(result.current?.utxoRows[0].excluded).toBe(false);
    expect(result.current?.utxoRows[1].excluded).toBe(true);
    expect(result.current?.utxoRows[1].exclusionReason).toBe("userExclusion");
  });

  it("should set isUsedInTx when utxo matches txInputs", () => {
    const utxo = createUtxo({ hash: "txHash1", outputIndex: 2 });
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [utxo] },
    });
    const status = createBitcoinStatus({
      txInputs: [
        {
          address: null,
          value: null,
          previousTxHash: "txHash1",
          previousOutputIndex: 2,
        },
      ],
    });

    const { result } = renderHook(() =>
      useBitcoinUtxoDisplayData({
        account,
        transaction: createBitcoinTransaction(),
        status,
        locale: "en",
      }),
    );

    expect(result.current?.utxoRows[0].isUsedInTx).toBe(true);
  });

  it("should set unconfirmed and disabled when exclusion reason is pickPendingUtxo", () => {
    const utxo = createUtxo({ blockHeight: undefined, isChange: false });
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [utxo] },
    });
    (getUTXOStatus as jest.Mock).mockReturnValue({
      excluded: true,
      reason: "pickPendingUtxo",
    });

    const { result } = renderHook(() =>
      useBitcoinUtxoDisplayData({
        account,
        transaction: createBitcoinTransaction(),
        status: createBitcoinStatus(),
        locale: "en",
      }),
    );

    expect(result.current?.utxoRows[0].unconfirmed).toBe(true);
    expect(result.current?.utxoRows[0].disabled).toBe(true);
    expect(result.current?.utxoRows[0].exclusionReason).toBe("pickPendingUtxo");
  });

  it("should format utxo value with locale", () => {
    const utxo = createUtxo({ value: new BigNumber(250000) });
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [utxo] },
    });

    const { result } = renderHook(() =>
      useBitcoinUtxoDisplayData({
        account,
        transaction: createBitcoinTransaction(),
        status: createBitcoinStatus(),
        locale: "fr",
      }),
    );

    expect(result.current?.utxoRows[0].formattedValue).toBe("250000 BTC");
  });

  it("should use totalSpent from status or default to zero", () => {
    const account = createBitcoinAccount({
      bitcoinResources: { utxos: [createUtxo()] },
    });
    const statusWithNoSpent = createBitcoinStatus({ totalSpent: undefined });

    const { result } = renderHook(() =>
      useBitcoinUtxoDisplayData({
        account,
        transaction: createBitcoinTransaction(),
        status: statusWithNoSpent,
        locale: "en",
      }),
    );

    expect(result.current?.totalSpent).toEqual(new BigNumber(0));
  });
});
