/**
 * Multi-user integration tests for /api/local/spaces/:id/python-packages.
 *
 * These cover the paths that DO NOT trigger a real pip install (auth, feature
 * gate, spec validation, listing) so the suite stays hermetic — no network,
 * no python3 required. The install/overlay mechanics are unit-tested in
 * engine/python-packages.test.ts.
 */
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { randomUUID } from 'node:crypto';
import express from 'express';
import request from 'supertest';
import { Repository } from '../db/repository.js';
import { createSpaceApi } from './space-api.js';
import type { PythonPackagesConfigShape } from '../config.js';

function freshSetup() {
  const dir = mkdtempSync(join(tmpdir(), 'pypkg-test-'));
  const repo = new Repository(join(dir, `${randomUUID()}.db`));
  return { repo, dir };
}

function makeApp(repo: Repository, currentUser: Express.User, pkgCfg?: PythonPackagesConfigShape) {
  const app = express();
  app.use((req: any, _res: any, next: any) => { req.user = currentUser; next(); });
  app.use('/', createSpaceApi({
    repo,
    dataRoot: tmpdir(),
    worktreeDir: tmpdir(),
    authActive: true,
    getPythonPackagesConfig: () => pkgCfg,
  }));
  return app;
}

async function seed(repo: Repository) {
  const manager = repo.createUser({ email: 'm@e.com', name: 'M', role: 'user', status: 'active' });
  const stranger = repo.createUser({ email: 's@e.com', name: 'S', role: 'user', status: 'active' });
  const space = await repo.createSpace({ kind: 'case', title: 'T', ownerId: manager.id, visibility: 'public' });
  return { manager, stranger, space };
}

function asExpressUser(user: { id: string; role: 'admin' | 'user' }): Express.User {
  return { id: user.id, role: user.role, orgIds: [] } as unknown as Express.User;
}

describe('/api/local/spaces/:id/python-packages', () => {
  let repo: Repository;
  let dir: string;
  let fx: Awaited<ReturnType<typeof seed>>;

  beforeEach(async () => {
    ({ repo, dir } = freshSetup());
    fx = await seed(repo);
  });
  afterEach(() => {
    repo.close();
    rmSync(dir, { recursive: true, force: true });
  });

  it('GET returns feature state + empty list for a viewer', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), { enabled: false });
    const res = await request(app).get(`/${fx.space.id}/python-packages`);
    expect(res.status).toBe(200);
    expect(res.body.enabled).toBe(false);
    expect(res.body.packages).toEqual([]);
  });

  it('GET never leaks indexUrl (may contain private-index credentials)', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), {
      enabled: true,
      indexUrl: 'https://user:secret-token@private.example/simple',
    });
    const res = await request(app).get(`/${fx.space.id}/python-packages`);
    expect(res.status).toBe(200);
    expect(res.body).not.toHaveProperty('indexUrl');
    expect(JSON.stringify(res.body)).not.toContain('secret-token');
  });

  it('POST from a non-manager stranger is 403 (no install attempted)', async () => {
    const app = makeApp(repo, asExpressUser(fx.stranger), { enabled: true });
    const res = await request(app).post(`/${fx.space.id}/python-packages`).send({ spec: 'requests' });
    expect(res.status).toBe(403);
  });

  it('POST is 400 when the feature is disabled', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), { enabled: false });
    const res = await request(app).post(`/${fx.space.id}/python-packages`).send({ spec: 'requests' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/disabled/);
  });

  it('POST rejects a stdlib-shadowing name with 400 (before any install)', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), { enabled: true });
    const res = await request(app).post(`/${fx.space.id}/python-packages`).send({ spec: 'json' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/standard library/);
  });

  it('POST rejects a shell-injection spec with 400', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), { enabled: true });
    const res = await request(app).post(`/${fx.space.id}/python-packages`).send({ spec: 'requests; curl evil' });
    expect(res.status).toBe(400);
  });

  it('DELETE of a not-installed package is 404', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), { enabled: true });
    const res = await request(app).delete(`/${fx.space.id}/python-packages/requests`);
    expect(res.status).toBe(404);
  });

  it('DELETE from a non-manager stranger is 403', async () => {
    const app = makeApp(repo, asExpressUser(fx.stranger), { enabled: true });
    const res = await request(app).delete(`/${fx.space.id}/python-packages/requests`);
    expect(res.status).toBe(403);
  });

  it('DELETE is 400 when the feature is disabled (no install path reachable)', async () => {
    const app = makeApp(repo, asExpressUser(fx.manager), { enabled: false });
    const res = await request(app).delete(`/${fx.space.id}/python-packages/requests`);
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/disabled/);
  });
});
