/**
 * @jest-environment jsdom
 */
import { genAccount } from "@ledgerhq/ledger-wallet-framework/mocks/account";
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
import { InvalidAddress } from "@ledgerhq/errors";
import { getMainAccount } from "@ledgerhq/live-common/account/index";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
import type { Account } from "@ledgerhq/types-live";
import { renderHook, waitFor } from "@testing-library/react";
import { BigNumber } from "bignumber.js";
import { useBridgeRecipientValidation } from "../useBridgeRecipientValidation";

jest.mock("@ledgerhq/live-common/bridge/index");
jest.mock("@ledgerhq/live-common/account/index");

const mockedGetMainAccount = jest.mocked(getMainAccount);
const mockedGetAccountBridge = jest.mocked(getAccountBridge);

const createMockAccount = (): Account => {
  const account = genAccount("mock_account");
  return {
    ...account,
    id: "mock_account_id",
    freshAddress: "source_address",
    balance: new BigNumber(100000000),
    spendableBalance: new BigNumber(100000000),
    currency: getCryptoCurrencyById("bitcoin"),
  };
};
const mockAccount = createMockAccount();

const mockBridge = {
  createTransaction: jest.fn(),
  updateTransaction: jest.fn(),
  prepareTransaction: jest.fn(),
  getTransactionStatus: jest.fn(),
  sync: jest.fn(),
  receive: jest.fn(),
  estimateMaxSpendable: jest.fn(),
  signOperation: jest.fn(),
  broadcast: jest.fn(),
  getPreloadStrategy: jest.fn(),
  checkValidRecipient: jest.fn(),
  signRawOperation: jest.fn(),
  validateAddress: jest.fn(),
  getSerializedAddressParameters: jest.fn(),
};

describe("useBridgeRecipientValidation", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    jest.useFakeTimers();
    mockedGetMainAccount.mockReturnValue(mockAccount);
    mockedGetAccountBridge.mockReturnValue(mockBridge);
    mockBridge.createTransaction.mockReturnValue({ recipient: "" });
    mockBridge.updateTransaction.mockImplementation((tx, updates) => ({ ...tx, ...updates }));
    mockBridge.prepareTransaction.mockResolvedValue({ recipient: "valid_address" });
    mockBridge.getTransactionStatus.mockResolvedValue({
      errors: {},
      warnings: {},
    });
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  it("returns empty state initially", () => {
    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "",
        account: null,
      }),
    );

    expect(result.current.errors).toEqual({});
    expect(result.current.warnings).toEqual({});
    expect(result.current.isLoading).toBe(false);
    expect(result.current.status).toBeNull();
  });

  it("validates recipient address successfully", async () => {
    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: mockAccount,
      }),
    );

    expect(result.current.isLoading).toBe(true);

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(mockBridge.createTransaction).toHaveBeenCalled();
    expect(mockBridge.updateTransaction).toHaveBeenCalledWith(expect.anything(), {
      recipient: "valid_address",
    });
    expect(mockBridge.prepareTransaction).toHaveBeenCalled();
    expect(mockBridge.getTransactionStatus).toHaveBeenCalled();
  });

  it("returns recipient error when bridge detects invalid address", async () => {
    const recipientError = new InvalidAddress();
    mockBridge.getTransactionStatus.mockResolvedValue({
      errors: { recipient: recipientError },
      warnings: {},
    });

    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "invalid_address",
        account: mockAccount,
      }),
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.errors.recipient).toBe(recipientError);
  });

  it("returns sender error when detected", async () => {
    const senderError = new Error("Insufficient balance");
    mockBridge.getTransactionStatus.mockResolvedValue({
      errors: { sender: senderError },
      warnings: {},
    });

    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: mockAccount,
      }),
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.errors.sender).toBe(senderError);
  });

  it("returns warnings when detected", async () => {
    const recipientWarning = new Error("Low balance warning");
    mockBridge.getTransactionStatus.mockResolvedValue({
      errors: {},
      warnings: { recipient: recipientWarning },
    });

    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: mockAccount,
      }),
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.warnings.recipient).toBe(recipientWarning);
  });

  it("does not validate when enabled is false", async () => {
    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: mockAccount,
        enabled: false,
      }),
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(mockBridge.createTransaction).not.toHaveBeenCalled();
  });

  it("resets state when recipient is cleared", async () => {
    const { result, rerender } = renderHook(
      ({ recipient }) =>
        useBridgeRecipientValidation({
          recipient,
          account: mockAccount,
        }),
      { initialProps: { recipient: "valid_address" } },
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    rerender({ recipient: "" });

    expect(result.current.errors).toEqual({});
    expect(result.current.warnings).toEqual({});
    expect(result.current.isLoading).toBe(false);
  });

  it("debounces validation calls", async () => {
    const { rerender } = renderHook(
      ({ recipient }) =>
        useBridgeRecipientValidation({
          recipient,
          account: mockAccount,
        }),
      { initialProps: { recipient: "addr1" } },
    );

    rerender({ recipient: "addr2" });
    rerender({ recipient: "addr3" });

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(mockBridge.createTransaction).toHaveBeenCalledTimes(1);
    });
  });

  it("handles validation errors gracefully", async () => {
    const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
    mockBridge.prepareTransaction.mockRejectedValue(new Error("Network error"));

    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: mockAccount,
      }),
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.errors).toEqual({});
    expect(consoleErrorSpy).toHaveBeenCalledWith(
      "Bridge recipient validation failed:",
      expect.any(Error),
    );

    consoleErrorSpy.mockRestore();
  });

  it("cancels pending validation when recipient changes", async () => {
    const { rerender } = renderHook(
      ({ recipient }) =>
        useBridgeRecipientValidation({
          recipient,
          account: mockAccount,
        }),
      { initialProps: { recipient: "addr1" } },
    );

    jest.advanceTimersByTime(150);

    rerender({ recipient: "addr2" });

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(mockBridge.updateTransaction).toHaveBeenLastCalledWith(expect.anything(), {
        recipient: "addr2",
      });
    });
  });

  it("returns null status when account is null", () => {
    const { result } = renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: null,
      }),
    );

    expect(result.current.status).toBeNull();
    // When account is null, the hook doesn't validate, so isLoading remains false
    // However, the initial state might set isLoading to true briefly
    expect(result.current.errors).toEqual({});
    expect(result.current.warnings).toEqual({});
  });

  it("handles parent account correctly", async () => {
    const parentAccount = { ...mockAccount, id: "parent_account_id" };

    renderHook(() =>
      useBridgeRecipientValidation({
        recipient: "valid_address",
        account: mockAccount,
        parentAccount,
      }),
    );

    jest.advanceTimersByTime(300);

    await waitFor(() => {
      expect(getMainAccount).toHaveBeenCalledWith(mockAccount, parentAccount);
    });
  });
});
