import { beforeEach, describe, expect, jest, test } from '@jest/globals';
import { EventName, Paddle } from '@paddle/paddle-node-sdk';
import {
  handlePaddleWebhook,
  paddleCancellationSession,
  paddleCheckoutSession,
  resolvePlanIdWithPaddle,
} from '../src/external-services/paddle';
import { sendIndieWelcomeEmailToUserId, sendProWelcomeEmailToUserId } from '../src/external-services/sendgrid';

const transactionQueryMock = jest.fn();

jest.mock('../src/server-utils/query', () => {
  return {
    default: jest.fn(),
    query: jest.fn(),
    transaction: jest.fn((cb: any) => cb(transactionQueryMock)),
  };
});

jest.mock('../src/external-services/sendgrid', () => {
  return {
    sendProWelcomeEmailToUserId: jest.fn(),
    sendIndieWelcomeEmailToUserId: jest.fn(),
  };
});

describe('plan id resolution', () => {
  test('gets a pro trial plan id', async () => {
    const PLAN_ID = 'PRO_TRIAL_PLAN_ID';
    const query = require('../src/server-utils/query').query;
    query.mockReturnValue(Promise.resolve({ rows: [{ id: PLAN_ID }] }));

    const id = await resolvePlanIdWithPaddle(process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID, true);
    expect(query).toHaveBeenCalledWith('SELECT id FROM plans WHERE paddle_price_id_env_var = $1 AND is_trial = $2', [
      'PADDLE_PRO_SUBSCRIPTION_PRICE_ID',
      true,
    ]);
    expect(id).toBe(PLAN_ID);
  });

  test('gets a pro plan id', async () => {
    const PLAN_ID = 'PRO_PLAN_ID';
    const query = require('../src/server-utils/query').query;
    query.mockReturnValue(Promise.resolve({ rows: [{ id: PLAN_ID }] }));

    const id = await resolvePlanIdWithPaddle(process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID, false);
    expect(query).toHaveBeenCalledWith('SELECT id FROM plans WHERE paddle_price_id_env_var = $1 AND is_trial = $2', [
      'PADDLE_PRO_SUBSCRIPTION_PRICE_ID',
      false,
    ]);
    expect(id).toBe(PLAN_ID);
  });

  test('gets a indie plan id', async () => {
    const PLAN_ID = 'INDIE_PLAN_ID';
    const query = require('../src/server-utils/query').query;
    query.mockReturnValue(Promise.resolve({ rows: [{ id: PLAN_ID }] }));

    const id = await resolvePlanIdWithPaddle(process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID, true);
    expect(query).toHaveBeenCalledWith('SELECT id FROM plans WHERE paddle_price_id_env_var = $1', [
      'PADDLE_INDIE_SUBSCRIPTION_PRICE_ID',
    ]);
    expect(id).toBe(PLAN_ID);
  });
});

describe('checkout and cancel endpoints', () => {
  beforeEach(() => jest.clearAllMocks());

  const sendMock = jest.fn();

  test('getting a paddle checkout session to send the right url ', async () => {
    await paddleCheckoutSession.func({ user: true, body: { plan: 'Pro' } }, { status: () => ({ send: sendMock }) }, {});
    expect(sendMock).toHaveBeenCalledWith({
      url: 'http://localhost:3001/checkout?priceId=pri_01j096gp6zhgyd66jj3zasrnhr',
    });
  });

  test('getting a paddle cancelation session to send the right url ', async () => {
    const query = require('../src/server-utils/query').query;
    query.mockReturnValue({ rows: [{ payload: { data: { id: 1234 } } }] });
    await paddleCancellationSession.func({ user: true }, { status: () => ({ send: sendMock }) }, {});
    expect(sendMock).toHaveBeenCalledWith({ url: 'http://cancel-subscription.com' });
  });
});

describe('paddle webhook handling', () => {
  beforeEach(() => jest.clearAllMocks());

  test('handlePaddleWebhook: save data from a webhook call', async () => {
    transactionQueryMock.mockReturnValue({ rows: [{ id: 1234 }] });
    const resSendStatusMock = jest.fn();

    await handlePaddleWebhook.func(
      {
        headers: { 'paddle-signature': 'test' },
        body: JSON.stringify({
          eventType: EventName.SubscriptionCreated,
          data: { customerId: 'paddle_id_1234', customData: { user_id: 1234 }, items: [{ price: { id: 'price_id' } }] },
        }),
      },
      { sendStatus: resSendStatusMock, status: jest.fn(() => ({ send: jest.fn() })) },
      {}
    );

    expect(resSendStatusMock).toHaveBeenCalledWith(200);
    expect(transactionQueryMock).toHaveBeenCalledWith(
      `INSERT INTO paddle_webhooks_events (paddle_customer_id, user_id, type, payload) VALUES ($1, $2, $3, $4)`,
      ['paddle_id_1234', 1234, EventName.SubscriptionCreated, expect.any(Object)]
    );
  });

  test('SubscriptionCreated: user creates pro subscription, gets welcome email', async () => {
    const email = 'keith@wavtool.com';
    const display_name = 'Keith Chia';

    transactionQueryMock.mockReturnValue({ rows: [{ email, display_name, id: 1234 }] });
    const resSendStatusMock = jest.fn();

    await handlePaddleWebhook.func(
      {
        headers: { 'paddle-signature': 'test' },
        body: JSON.stringify({
          eventType: EventName.SubscriptionCreated,
          data: {
            customerId: 'paddle_id_1234',
            customData: { user_id: 1234 },
            items: [{ price: { id: process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID } }],
          },
        }),
      },
      { sendStatus: resSendStatusMock, status: jest.fn(() => ({ send: jest.fn() })) },
      {}
    );

    expect(resSendStatusMock).toHaveBeenCalledWith(200);
    expect(sendIndieWelcomeEmailToUserId).not.toHaveBeenCalled();
    expect(sendProWelcomeEmailToUserId).toHaveBeenCalledWith(email, display_name);
  });

  test('SubscriptionCreated: user creates indie subscription, gets welcome email', async () => {
    const email = 'keith@wavtool.com';
    const display_name = 'Keith Chia';

    transactionQueryMock.mockReturnValue({ rows: [{ email, display_name, id: 1234 }] });
    const resSendStatusMock = jest.fn();

    await handlePaddleWebhook.func(
      {
        headers: { 'paddle-signature': 'test' },
        body: JSON.stringify({
          eventType: EventName.SubscriptionCreated,
          data: {
            customerId: 'paddle_id_1234',
            customData: { user_id: 1234 },
            items: [{ price: { id: process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID } }],
          },
        }),
      },
      { sendStatus: resSendStatusMock, status: jest.fn(() => ({ send: jest.fn() })) },
      {}
    );

    expect(resSendStatusMock).toHaveBeenCalledWith(200);
    expect(sendIndieWelcomeEmailToUserId).toHaveBeenCalledWith(email, display_name);
    expect(sendProWelcomeEmailToUserId).not.toHaveBeenCalled();
  });

  test.skip('SubscriptionCreated: user upgrades existing indie to pro subscription', async () => {
    // we are no longer doing it this way, test needs a rewrite
    const email = 'keith@wavtool.com';
    const display_name = 'Keith Chia';
    const previousSubscriptionId = 'prev_sub_id';
    transactionQueryMock
      .mockReturnValueOnce({ rows: [{ email, display_name, id: 1234 }] })
      .mockReturnValueOnce({ rows: [{ email, display_name, id: 1234 }] })
      .mockReturnValueOnce({ rows: [{ email, display_name, id: 1234 }] })
      .mockReturnValueOnce({ rows: [{ payload: { data: { id: previousSubscriptionId, status: 'active' } } }] }); // return existing subscription to indicate upgrade

    const resSendStatusMock = jest.fn();

    await handlePaddleWebhook.func(
      {
        headers: { 'paddle-signature': 'test' },
        body: JSON.stringify({
          eventType: EventName.SubscriptionCreated,
          data: {
            customerId: 'paddle_id_1234',
            customData: { user_id: 1234 },
            items: [{ price: { id: process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID } }],
          },
        }),
      },
      { sendStatus: resSendStatusMock, status: jest.fn(() => ({ send: jest.fn() })) },
      {}
    );

    const paddle = new Paddle('test', {});
    expect(resSendStatusMock).toHaveBeenCalledWith(200);
    expect(paddle.subscriptions.cancel).toHaveBeenCalledWith(previousSubscriptionId, { effectiveFrom: 'immediately' });
  });

  // use cases
  // - subscription
  // - trials
  // - cancellations
  // - upgrades/downgrades
  // - resubs
});
