process.env.VAPID_PUBLIC_KEY = 'BTestPublicKey_______________________________________________________________';
process.env.VAPID_PRIVATE_KEY = 'TestPrivateKey_______________________________';
process.env.VAPID_SUBJECT = 'mailto:test@vyndr.app';

const mockSendNotification = jest.fn();
const mockSetVapidDetails = jest.fn();

jest.mock('web-push', () => ({
  setVapidDetails: (...args) => mockSetVapidDetails(...args),
  sendNotification: (...args) => mockSendNotification(...args),
}));

// Builds a fluent supabase mock where each chained call records intent and
// the terminal promise yields {data, error}. Good enough for what webPush
// actually does (.from().select().eq() / .delete().eq()).
function makeSupabase({ rows = [], error = null } = {}) {
  const deleted = [];
  const builder = (table) => {
    const ctx = { table, filters: [] };
    const proxy = {
      _ctx: ctx,
      select: () => proxy,
      eq: (col, val) => {
        ctx.filters.push([col, val]);
        return ctx.action === 'delete' ? Promise.resolve({ error: null }) : proxy;
      },
      contains: () => proxy,
      delete: () => {
        ctx.action = 'delete';
        deleted.push(ctx);
        return proxy;
      },
      then: (resolve) => resolve({ data: rows, error }),
    };
    return proxy;
  };
  return {
    from: jest.fn().mockImplementation(builder),
    _deleted: deleted,
  };
}

const mockSupabase = { current: makeSupabase() };
jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => mockSupabase.current,
}));

const webPush = require('../../src/services/distribution/webPush');

beforeEach(() => {
  mockSendNotification.mockReset();
  mockSetVapidDetails.mockReset();
  mockSupabase.current = makeSupabase();
});

describe('webPush.configured', () => {
  test('returns true when both VAPID keys present', () => {
    expect(webPush.configured()).toBe(true);
  });
});

describe('webPush.sendPushToUser', () => {
  test('returns sent=0 when user has no subscriptions', async () => {
    mockSupabase.current = makeSupabase({ rows: [] });
    const result = await webPush.sendPushToUser('user-1', { title: 'hi' });
    expect(result).toMatchObject({ ok: true, sent: 0 });
    expect(mockSendNotification).not.toHaveBeenCalled();
  });

  test('sends to every subscription and counts successes', async () => {
    mockSupabase.current = makeSupabase({
      rows: [
        { id: 'a', endpoint: 'https://a', keys_p256dh: 'p1', keys_auth: 'k1' },
        { id: 'b', endpoint: 'https://b', keys_p256dh: 'p2', keys_auth: 'k2' },
      ],
    });
    mockSendNotification.mockResolvedValue({ statusCode: 201 });
    const result = await webPush.sendPushToUser('user-1', { title: 'hi' });
    expect(result.ok).toBe(true);
    expect(result.sent).toBe(2);
    expect(mockSendNotification).toHaveBeenCalledTimes(2);
  });

  test('prunes subscription that returns 410 Gone', async () => {
    mockSupabase.current = makeSupabase({
      rows: [{ id: 'dead', endpoint: 'https://dead', keys_p256dh: 'p', keys_auth: 'k' }],
    });
    const err = Object.assign(new Error('Gone'), { statusCode: 410 });
    mockSendNotification.mockRejectedValue(err);
    const result = await webPush.sendPushToUser('user-1', { title: 'hi' });
    expect(result.pruned).toBe(1);
    expect(result.sent).toBe(0);
    expect(mockSupabase.current._deleted.length).toBeGreaterThan(0);
  });

  test('non-410 failure does not prune, counts as failed', async () => {
    mockSupabase.current = makeSupabase({
      rows: [{ id: 'flaky', endpoint: 'https://flaky', keys_p256dh: 'p', keys_auth: 'k' }],
    });
    const err = Object.assign(new Error('boom'), { statusCode: 500 });
    mockSendNotification.mockRejectedValue(err);
    const result = await webPush.sendPushToUser('user-1', { title: 'hi' });
    expect(result.failed).toBe(1);
    expect(result.sent).toBe(0);
    expect(mockSupabase.current._deleted.length).toBe(0);
  });
});
