import { beforeEach, describe, expect, it, vi } from "vitest";
import mockRequire from "mock-require";

const gqlRequestMock = { request: vi.fn() };
const getPaymentTypeMock = vi.fn(() => "Visa");
const handlePaymentValidationErrorMock = vi.fn();

let handleInvoiceBasedPayment;

beforeEach(() => {
  vi.resetModules();
  vi.clearAllMocks();

  mockRequire("../../../graphql-client/graphql-client", {
    client: gqlRequestMock
  });

  mockRequire("../getPaymentType", getPaymentTypeMock);
  mockRequire("../handlePaymentValidationError", handlePaymentValidationErrorMock);

  handleInvoiceBasedPayment = require("../handleInvoiceBasedPayment");

  gqlRequestMock.request
    .mockResolvedValueOnce({
      jobs: [
        {
          id: "job123",
          bodyshop: {
            id: "shop123",
            intellipay_config: {
              payment_map: {
                visa: "Visa"
              }
            }
          }
        }
      ]
    })
    .mockResolvedValueOnce({
      insert_payments: {
        returning: [{ id: "payment123" }]
      }
    })
    .mockResolvedValueOnce({
      insert_payment_response: {
        returning: [{ id: "response123" }]
      }
    });
});

describe("handleInvoiceBasedPayment", () => {
  const mockLogger = { log: vi.fn() };
  const mockRes = { sendStatus: vi.fn() };

  const values = {
    merchantid: "m123",
    invoice: "INV-001",
    total: 100.0,
    authcode: "AUTH123",
    cardtype: "visa",
    paymentid: "P789"
  };

  const logMeta = { op: "abc123" };

  it("processes a valid invoice-based payment", async () => {
    await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);

    expect(gqlRequestMock.request).toHaveBeenCalledTimes(3);
    expect(getPaymentTypeMock).toHaveBeenCalledWith({ visa: "Visa" }, "visa");
    expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
    expect(handlePaymentValidationErrorMock).not.toHaveBeenCalled();
  });

  it("handles missing merchantid with validation error", async () => {
    const invalidValues = { ...values, merchantid: undefined };

    await handleInvoiceBasedPayment(invalidValues, mockLogger, logMeta, mockRes);

    expect(handlePaymentValidationErrorMock).toHaveBeenCalledWith(
      mockRes,
      mockLogger,
      "intellipay-postback-no-merchantid",
      "Merchant ID is missing",
      logMeta
    );
    expect(gqlRequestMock.request).not.toHaveBeenCalled();
  });

  it("handles job not found with validation error", async () => {
    gqlRequestMock.request.mockReset();
    gqlRequestMock.request.mockResolvedValueOnce({ jobs: [] });

    await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);

    expect(handlePaymentValidationErrorMock).toHaveBeenCalledWith(
      mockRes,
      mockLogger,
      "intellipay-postback-job-not-found",
      "Job not found",
      logMeta,
      200
    );
  });

  it("handles missing bodyshop with validation error", async () => {
    gqlRequestMock.request.mockReset();
    gqlRequestMock.request.mockResolvedValueOnce({
      jobs: [{ id: "job123", bodyshop: null }]
    });

    await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);

    expect(handlePaymentValidationErrorMock).toHaveBeenCalledWith(
      mockRes,
      mockLogger,
      "intellipay-postback-bodyshop-not-found",
      "Bodyshop not found",
      logMeta
    );
  });

  it("logs all expected stages of the process", async () => {
    await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);

    const logTags = mockLogger.log.mock.calls.map(([tag]) => tag);

    expect(logTags).toContain("intellipay-postback-invoice-job-fetched");
    expect(logTags).toContain("intellipay-postback-invoice-payment-success");
    expect(logTags).toContain("intellipay-postback-invoice-response-success");
  });
});
