import request from 'supertest';
import { Harness, TenantClient } from './harness';

describe('Multi-tenancy, RLS isolation, RBAC, feature flags and plan limits', () => {
  const h = new Harness();
  let a: TenantClient;
  let b: TenantClient;
  let aStorer: string;

  beforeAll(async () => {
    await h.start();
    a = await h.tenant('ten-a');
    b = await h.tenant('ten-b', 'STARTER');
    aStorer = (await a.standardWorld()).storer.id;
  });
  afterAll(() => h.stop());

  it('rejects anonymous and malformed credentials', async () => {
    await request(h.server).get('/api/storers').expect(401);
    await request(h.server).get('/api/storers').set('authorization', 'Bearer nope').expect(401);
    await request(h.server).post('/api/auth/login').send({ tenantCode: a.code, email: a.email, password: 'wrong' }).expect(401);
    await request(h.server).post('/api/auth/login').send({ tenantCode: b.code, email: a.email, password: a.password }).expect(401);
  });

  it('tenant B cannot read or address tenant A rows, even by id', async () => {
    const mine = (await a.get('/storers').expect(200)).body.items;
    expect(mine.map((s: { code: string }) => s.code)).toContain('ACME');
    const theirs = (await b.get('/storers').expect(200)).body.items;
    expect(theirs.map((s: { code: string }) => s.code)).not.toContain('ACME');
    await b.get(`/storers/${aStorer}`).expect(404);
    const skus = (await b.get('/skus').expect(200)).body.items;
    expect(skus).toHaveLength(0);
    // writing against a foreign id is also impossible (tenant_ref_guard trigger)
    const res = await b.post('/skus', { storerId: aStorer, code: 'X', description: 'x' });
    expect([404, 409, 422]).toContain(res.status);
    expect((await b.get('/skus').expect(200)).body.items).toHaveLength(0);
  });

  it('platform staff see every tenant with counts; tenant users cannot reach the platform console', async () => {
    const pt = await h.platformToken();
    const tenants = (await a.get('/platform/tenants', undefined, pt).expect(200)).body;
    const row = tenants.find((t: { code: string }) => t.code === a.code);
    expect(row.userCount).toBe(1);
    expect(row.warehouseCount).toBe(1);
    expect(row.storerCount).toBe(1);
    await a.get('/platform/tenants').expect(403);
  });

  it('feature flags gate modules per plan (STARTER has no AI, no SAP)', async () => {
    expect(a.profile.features).toContain('ai.nl_query');
    expect(b.profile.features).not.toContain('ai.nl_query');
    const res = await b.post('/ai/copilot/ask', { question: 'hello' }).expect(403);
    expect(res.body.code).toBe('FEATURE_DISABLED');
    await a.get('/ai/status').expect(200);
  });

  it('plan limits: STARTER cannot exceed 1 warehouse', async () => {
    const res = await b.post('/warehouses', { code: 'KW2', name: 'Second' }).expect(422);
    expect(res.body.code).toBe('PLAN_LIMIT');
  });

  it('RBAC: a warehouseman cannot create master data or decide AI proposals', async () => {
    const roles = (await a.get('/roles').expect(200)).body;
    const wm = roles.find((r: { code: string }) => r.code === 'WAREHOUSEMAN');
    await a.post('/users', { email: 'rf@a.test', name: 'RF User', password: 'RfUser!Pass2026', roleIds: [wm.id], warehouseIds: [a.warehouseId] }).expect(201);
    const login = await request(h.server).post('/api/auth/login').send({ tenantCode: a.code, email: 'rf@a.test', password: 'RfUser!Pass2026' }).expect(200);
    const tok = login.body.tokens.accessToken;
    await a.get('/inventory/balances', { warehouseId: a.warehouseId }, tok).expect(200);
    const denied = await a.post('/skus', { storerId: aStorer, code: 'NOPE', description: 'x' }, tok).expect(403);
    expect(denied.body.message).toMatch(/permission/i);
    await a.post('/ai/proposals/00000000-0000-0000-0000-000000000000/decide', { decision: 'REJECT' }, tok).expect(403);
  });

  it('storer-scoped portal users only see their own storer', async () => {
    const other = (await a.post('/storers', { code: 'OTHER', name: 'Other Co' }).expect(201)).body;
    const roles = (await a.get('/roles').expect(200)).body;
    const customer = roles.find((r: { code: string }) => r.code === 'CUSTOMER');
    await a.post('/users', { email: 'portal@acme.test', name: 'Acme Portal', password: 'Portal!Pass2026', roleIds: [customer.id], warehouseIds: [a.warehouseId], storerId: aStorer }).expect(201);
    const login = await request(h.server).post('/api/auth/login').send({ tenantCode: a.code, email: 'portal@acme.test', password: 'Portal!Pass2026' }).expect(200);
    const tok = login.body.tokens.accessToken;
    const skus = (await a.get('/skus', undefined, tok).expect(200)).body.items;
    expect(skus.every((s: { storerId: string }) => s.storerId === aStorer)).toBe(true);
    const forbidden = await a.get('/skus', { storerId: other.id }, tok).expect(403);
    expect(forbidden.body.code).toBe('STORER_SCOPE');
  });

  it('refresh token rotation: old refresh token is rejected after use', async () => {
    const login = await request(h.server).post('/api/auth/login').send({ tenantCode: a.code, email: a.email, password: a.password }).expect(200);
    const rt = login.body.tokens.refreshToken;
    const r1 = await request(h.server).post('/api/auth/refresh').send({ refreshToken: rt }).expect(200);
    expect(r1.body.tokens.accessToken).toBeTruthy();
    await request(h.server).post('/api/auth/refresh').send({ refreshToken: rt }).expect(401);
  });

  it('API keys: scopes are enforced, daily plan quota returns 429', async () => {
    const created = (await a.post('/api-keys', { name: 'ERP', storerId: aStorer, scopes: ['inventory:read'] }).expect(201)).body;
    const k = a.apiKey(created.key);
    await k.get('/v1/inventory', { groupBy: 'ITEM' }).expect(200);
    await k.post('/v1/asns', { warehouseCode: 'KW1', reference: 'X', lines: [] }).expect(403); // lacks asn:write
    await k.get('/users').expect(403); // keys never reach back-office endpoints
    // shrink the daily quota and hit it
    const pt = await h.platformToken();
    const tenants = (await a.get('/platform/tenants', undefined, pt).expect(200)).body;
    const me = tenants.find((t: { code: string }) => t.code === a.code);
    const plans = (await a.get('/platform/plans', undefined, pt).expect(200)).body;
    const tiny = (await a.post('/platform/plans', { code: 'TINY', name: 'Tiny', limits: { maxWarehouses: 1, maxUsers: 5, maxStorers: 5, maxApiCallsPerDay: 2 }, features: plans.find((p: { code: string }) => p.code === 'ENTERPRISE').features }, pt).expect(201)).body;
    await a.patch(`/platform/tenants/${me.id}/subscription`, { planCode: tiny.code }, pt).expect(200);
    await k.get('/v1/inventory', { groupBy: 'ITEM' }).expect(200);
    const limited = await k.get('/v1/inventory', { groupBy: 'ITEM' }).expect(429);
    expect(limited.body.code).toBe('PLAN_LIMIT');
  });

  it('audit log records who did what', async () => {
    const audit = (await a.get('/audit', { entityType: 'storer' }).expect(200)).body;
    expect(audit.total).toBeGreaterThanOrEqual(2);
    expect(audit.items[0].actorName).toBeTruthy();
    expect(audit.items[0].action).toMatch(/^storer\./);
  });
});
