import prepareTransferMock from "@ledgerhq/hw-app-canton/tests/fixtures/prepare-transfer.json";
import { craftTransaction } from "../common-logic";
import {
  createMockCantonAccount,
  createMockCantonSigner,
  createMockSignerContext,
  createMockTransaction,
} from "../test/fixtures";
import { Transaction } from "../types";
import { buildSignOperation } from "./signOperation";

jest.mock("../common-logic", () => {
  const actual = jest.requireActual("../common-logic");
  return {
    ...actual,
    craftTransaction: jest.fn(),
  };
});

const {
  json: { transaction, metadata },
} = prepareTransferMock;
const mockCraftTransaction = craftTransaction as jest.MockedFunction<typeof craftTransaction>;

describe("buildSignOperation", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockCraftTransaction.mockReset();
  });

  const mockAccount = createMockCantonAccount();
  const mockTransaction = createMockTransaction();
  const mockDeviceId = "test-device-id";

  it("should use default expireInSeconds (1 day) when not provided in transaction", async () => {
    // GIVEN
    const mockSigner = createMockCantonSigner();
    const mockSignerContext = jest.fn().mockImplementation(async (_deviceId, callback) => {
      return await callback(mockSigner);
    });

    mockCraftTransaction.mockResolvedValue({
      nativeTransaction: prepareTransferMock.json,
      serializedTransaction: prepareTransferMock.serialized,
      hash: prepareTransferMock.hash,
    });

    const signOperation = buildSignOperation(mockSignerContext);
    const transactionWithoutExpiry: Transaction = { ...mockTransaction };
    delete transactionWithoutExpiry.expireInSeconds;

    // WHEN
    await new Promise((resolve, reject) => {
      signOperation({
        account: mockAccount,
        deviceId: mockDeviceId,
        transaction: transactionWithoutExpiry,
      }).subscribe({
        next: value => {
          if (value.type === "signed") {
            resolve(value);
          }
        },
        error: reject,
      });
    });

    // THEN
    expect(mockCraftTransaction).toHaveBeenCalledWith(
      mockAccount.currency,
      expect.any(Object),
      expect.objectContaining({
        expireInSeconds: 24 * 60 * 60, // Default 1 day
      }),
    );
  });

  it("should use custom expireInSeconds when provided in transaction", async () => {
    // GIVEN
    const mockSigner = createMockCantonSigner();
    const mockSignerContext = jest.fn().mockImplementation(async (_deviceId, callback) => {
      return await callback(mockSigner);
    });

    mockCraftTransaction.mockResolvedValue({
      nativeTransaction: prepareTransferMock.json,
      serializedTransaction: prepareTransferMock.serialized,
      hash: prepareTransferMock.hash,
    });

    const signOperation = buildSignOperation(mockSignerContext);
    const customExpireSeconds = 3 * 60 * 60; // 3 hours
    const transactionWithExpiry: Transaction = {
      ...mockTransaction,
      expireInSeconds: customExpireSeconds,
    };

    // WHEN
    await new Promise((resolve, reject) => {
      signOperation({
        account: mockAccount,
        deviceId: mockDeviceId,
        transaction: transactionWithExpiry,
      }).subscribe({
        next: value => {
          if (value.type === "signed") {
            resolve(value);
          }
        },
        error: reject,
      });
    });

    // THEN
    expect(mockCraftTransaction).toHaveBeenCalledWith(
      mockAccount.currency,
      expect.any(Object),
      expect.objectContaining({
        expireInSeconds: customExpireSeconds,
      }),
    );
  });

  it("should handle prepared transaction signing", async () => {
    // GIVEN
    const mockSigner = createMockCantonSigner();
    const mockSignerContext = createMockSignerContext(mockSigner);

    mockCraftTransaction.mockResolvedValue({
      nativeTransaction: {
        json: { transaction, metadata },
        serialized: "serialized-transaction",
        hash: "mock-hash",
        step: { type: "single-step" },
      },
      serializedTransaction: "serialized-transaction",
      hash: "mock-hash",
    });

    const signOperation = buildSignOperation(mockSignerContext);

    // WHEN
    const result = await new Promise((resolve, reject) => {
      signOperation({
        account: mockAccount,
        deviceId: mockDeviceId,
        transaction: mockTransaction,
      }).subscribe({
        next: value => {
          if (value.type === "signed") {
            resolve(value);
          }
        },
        error: reject,
      });
    });

    // THEN
    expect(mockCraftTransaction).toHaveBeenCalled();
    expect(result).toMatchObject({ signedOperation: { signature: expect.any(String) } });
    const signedResult = result as { signedOperation: { signature: string } };
    const parsedSignature = JSON.parse(signedResult.signedOperation.signature);
    expect(parsedSignature).toMatchObject({
      serialized: "serialized-transaction",
      signature: expect.stringMatching(
        new RegExp(`^[0-9a-f]+__PARTY__${mockAccount.freshAddress}$`, "i"),
      ),
    });
  });
});
