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

const gqlRequestMock = { request: vi.fn() };
const getPaymentTypeMock = vi.fn(() => "American Express");
const sendPaymentNotificationEmailMock = vi.fn();

let handleCommentBasedPayment;

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

  // Mock dependencies using mock-require BEFORE requiring the target module
  mockRequire("../../../graphql-client/graphql-client", {
    client: gqlRequestMock
  });

  mockRequire("../getPaymentType", getPaymentTypeMock);
  mockRequire("../sendPaymentNotificationEmail", sendPaymentNotificationEmailMock);

  // Now require the module under test
  handleCommentBasedPayment = require("../handleCommentBasedPayment");

  // Chain your GraphQL mocks
  gqlRequestMock.request
    .mockResolvedValueOnce({
      jobs: [
        {
          id: "c1ffe09c-e7d4-46b3-aac5-f23e39563181",
          shopid: "bfec8c8c-b7f1-49e0-be4c-524455f4e582"
        }
      ]
    })
    .mockResolvedValueOnce({
      bodyshops_by_pk: {
        id: "bfec8c8c-b7f1-49e0-be4c-524455f4e582",
        intellipay_config: {
          payment_map: {
            amex: "American Express"
          }
        }
      }
    })
    .mockResolvedValueOnce({
      insert_payments: {
        returning: [{ id: "5dfda3c4-c0a6-4b09-a73d-176ed0ac6499" }]
      }
    });
});

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

  const values = {
    authcode: "5557301",
    total: "0.01",
    origin: "Dejavoo",
    paymentid: "24294378",
    cardtype: "Amex"
  };

  const decodedComment = {
    payments: [{ jobid: "c1ffe09c-e7d4-46b3-aac5-f23e39563181", amount: 0.01 }],
    userEmail: "test@example.com"
  };

  const logMeta = { op: "xyz123" };

  it("processes comment-based payment and returns 200", async () => {
    await handleCommentBasedPayment(values, decodedComment, mockLogger, logMeta, mockRes);

    expect(gqlRequestMock.request).toHaveBeenCalledTimes(3);
    expect(getPaymentTypeMock).toHaveBeenCalledWith({ amex: "American Express" }, "Amex");
    expect(sendPaymentNotificationEmailMock).not.toHaveBeenCalled();
    expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
  });

  it("sends notification if origin is OneLink and userEmail exists", async () => {
    const oneLinkValues = { ...values, origin: "OneLink" };

    await handleCommentBasedPayment(oneLinkValues, decodedComment, mockLogger, logMeta, mockRes);

    expect(sendPaymentNotificationEmailMock).toHaveBeenCalledWith(
      "test@example.com",
      expect.anything(),
      expect.anything(),
      mockLogger,
      logMeta
    );

    expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
  });

  it("handles decodedComment as a direct array", async () => {
    const arrayComment = [{ jobid: "c1ffe09c-e7d4-46b3-aac5-f23e39563181", amount: 0.01 }];

    await handleCommentBasedPayment(values, arrayComment, mockLogger, logMeta, mockRes);

    expect(gqlRequestMock.request).toHaveBeenCalledTimes(3);
    expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
  });

  it("does not send email if origin is OneLink but userEmail is missing", async () => {
    const commentWithoutEmail = {
      payments: decodedComment.payments
      // no userEmail
    };

    const oneLinkValues = { ...values, origin: "OneLink" };

    await handleCommentBasedPayment(oneLinkValues, commentWithoutEmail, mockLogger, logMeta, mockRes);

    expect(sendPaymentNotificationEmailMock).not.toHaveBeenCalled();
    expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
  });

  it("logs important stages of the process", async () => {
    await handleCommentBasedPayment(values, decodedComment, mockLogger, logMeta, mockRes);

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

    expect(logCalls).toContain("intellipay-postback-parsed-comment");
    expect(logCalls).toContain("intellipay-postback-payment-success");
  });

  it("handles missing payment_map safely", async () => {
    gqlRequestMock.request.mockReset(); // 🧹 Clear previous .mockResolvedValueOnce calls

    gqlRequestMock.request
      .mockResolvedValueOnce({
        jobs: [{ id: "job1", shopid: "shop1" }]
      })
      .mockResolvedValueOnce({
        bodyshops_by_pk: {
          id: "shop1",
          intellipay_config: null
        }
      })
      .mockResolvedValueOnce({
        insert_payments: {
          returning: [{ id: "payment1" }]
        }
      });

    await handleCommentBasedPayment(values, decodedComment, mockLogger, logMeta, mockRes);

    expect(getPaymentTypeMock).toHaveBeenCalledWith(undefined, "Amex");
    expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
  });
});
