const { scanLimit, __internals } = require('../../src/middleware/scanLimit');

function mockReqRes({ ip = '1.1.1.1', tier, userId } = {}) {
  const req = {
    ip,
    socket: { remoteAddress: ip },
    headers: {},
    user: userId ? { id: userId, tier } : (tier ? { tier } : null),
  };
  const headers = {};
  const res = {
    statusCode: 200,
    body: null,
    set(k, v) { headers[k] = v; return res; },
    status(c) { res.statusCode = c; return res; },
    json(b) { res.body = b; return res; },
  };
  return { req, res, headers };
}

beforeEach(() => __internals.resetForTests());

describe('scanLimit middleware', () => {
  const mw = scanLimit();

  test('free user allowed up to 3 scans per day', () => {
    const next = jest.fn();
    for (let i = 0; i < 3; i += 1) {
      const { req, res } = mockReqRes({ tier: 'free', userId: 'u-free' });
      mw(req, res, next);
    }
    expect(next).toHaveBeenCalledTimes(3);
  });

  test('free user 4th scan in the window returns 429', () => {
    const next = jest.fn();
    let lastRes;
    for (let i = 0; i < 4; i += 1) {
      const { req, res } = mockReqRes({ tier: 'free', userId: 'u-free-block' });
      mw(req, res, next);
      lastRes = res;
    }
    expect(next).toHaveBeenCalledTimes(3);
    expect(lastRes.statusCode).toBe(429);
    expect(lastRes.body.error).toMatch(/scan limit/i);
    expect(lastRes.body.scans_used).toBe(3);
    expect(lastRes.body.scans_limit).toBe(3);
    expect(lastRes.body.tier).toBe('free');
  });

  test('429 response includes Retry-After + X-Scans-* headers', () => {
    const next = jest.fn();
    let lastHeaders;
    for (let i = 0; i < 4; i += 1) {
      const { req, res, headers } = mockReqRes({ tier: 'free', userId: 'u-hdr' });
      mw(req, res, next);
      lastHeaders = headers;
    }
    expect(parseInt(lastHeaders['Retry-After'], 10)).toBeGreaterThan(0);
    expect(lastHeaders['X-Scans-Used']).toBe('3');
    expect(lastHeaders['X-Scans-Limit']).toBe('3');
  });

  // Session 62 (A1-S1) — analyst is UNLIMITED now (the Founder card promises
  // "Unlimited reads"; the 15/day cap made the pricing page false).
  test('analyst tier is unlimited (Founder promise)', () => {
    const next = jest.fn();
    let blockedAt = null;
    for (let i = 0; i < 30; i += 1) {
      const { req, res } = mockReqRes({ tier: 'analyst', userId: 'u-analyst' });
      mw(req, res, next);
      if (res.statusCode === 429 && blockedAt == null) blockedAt = i;
    }
    expect(blockedAt).toBeNull();
    expect(next).toHaveBeenCalledTimes(30);
  });

  test('desk tier is unlimited — never blocks', () => {
    const next = jest.fn();
    for (let i = 0; i < 100; i += 1) {
      const { req, res } = mockReqRes({ tier: 'desk', userId: 'u-desk' });
      mw(req, res, next);
      expect(res.statusCode).toBe(200);
    }
    expect(next).toHaveBeenCalledTimes(100);
  });

  test('anonymous user (no req.user) falls back to free quota keyed by IP', () => {
    const next = jest.fn();
    let lastRes;
    for (let i = 0; i < 4; i += 1) {
      const { req, res } = mockReqRes({ ip: '7.7.7.7' });
      mw(req, res, next);
      lastRes = res;
    }
    expect(next).toHaveBeenCalledTimes(3);
    expect(lastRes.statusCode).toBe(429);
  });

  test('different IPs are independent (anonymous bucket)', () => {
    const next = jest.fn();
    const a = mockReqRes({ ip: '1.1.1.1' });
    const b = mockReqRes({ ip: '2.2.2.2' });
    mw(a.req, a.res, next);
    mw(b.req, b.res, next);
    expect(next).toHaveBeenCalledTimes(2);
    expect(a.res.statusCode).toBe(200);
    expect(b.res.statusCode).toBe(200);
  });

  test('authenticated users in the same IP get independent quotas', () => {
    const next = jest.fn();
    // Same IP, different user IDs — should not interfere.
    for (let i = 0; i < 3; i += 1) {
      const { req, res } = mockReqRes({ tier: 'free', userId: 'u-A', ip: '5.5.5.5' });
      mw(req, res, next);
    }
    // User A exhausted; user B starts clean.
    const { req, res } = mockReqRes({ tier: 'free', userId: 'u-B', ip: '5.5.5.5' });
    mw(req, res, next);
    expect(res.statusCode).toBe(200);
    expect(next).toHaveBeenCalledTimes(4);
  });
});
