/**
 * Admin user-management with local accounts: create, password reset, and the
 * deletion path (folder removal + local-user guard).
 *
 * Uses a real admin session (request.agent cookie jar) so requireAdmin passes.
 * See docs/superpowers/plans/2026-06-09-local-auth.md.
 */
import { afterAll, beforeAll, describe, it, expect } from 'vitest';
import { mkdtempSync, rmSync, existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import express, { type Express } from 'express';
import request from 'supertest';
import { Repository } from '../db/repository.js';
import { runMigrations } from '../db/migrate.js';
import { setupAuth, requireAuth, buildChangePasswordHandler } from './auth.js';
import { mountAdminApi } from './admin-api.js';
import { ensureUserFolder } from '../user-folder/paths.js';
import type { AuthConfig } from '../config.js';

const AUTH: AuthConfig = {
  sessionSecret: 'test', sessionMaxAge: 3600_000, secureCookie: false,
  adminEmails: [], providers: {}, local: { enabled: true },
};

describe('admin-api local user management', () => {
  let tempDir = '';
  let userFolderRoot = '';
  let repo: Repository;
  let app: Express;

  // setupAuth ONCE per file: passport.serializeUser/deserializeUser register
  // onto a stack (they don't replace), so calling setupAuth per-test would
  // leave earlier tests' deserializers (bound to closed repos) running first.
  // The server only ever calls setupAuth once, so this mirrors production.
  beforeAll(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-adminlocal-'));
    userFolderRoot = join(tempDir, 'users');
    repo = new Repository(join(tempDir, 'orchestrator.db'));
    runMigrations(repo.getDb());
    repo.upsertLocalSystemAdmin({ email: 'admin@x.com', password: 'adminpw12' });

    app = express();
    const auth = setupAuth(repo, AUTH);
    app.use(auth.sessionMiddleware);
    app.use(auth.passportInit);
    app.use(auth.passportSession);
    app.use('/auth', auth.authRouter);
    app.use('/api/admin', express.json());
    mountAdminApi(app, repo, true, userFolderRoot);
    // Self-service password change (mounted the same way server.ts does).
    app.post('/api/auth/password', requireAuth, express.json(), buildChangePasswordHandler(repo));
  });
  afterAll(() => {
    repo.close();
    if (tempDir) { rmSync(tempDir, { recursive: true, force: true }); tempDir = ''; }
  });

  async function adminAgent() {
    const agent = request.agent(app);
    await agent.post('/auth/local').type('form').send({ email: 'admin@x.com', password: 'adminpw12' });
    return agent;
  }

  it('POST creates a local user (active) that can authenticate', async () => {
    const agent = await adminAgent();
    const res = await agent.post('/api/admin/users').send({ email: 'bob@x.com', password: 'bobpw1234' });
    expect(res.status).toBe(201);
    const u = repo.getUserByEmail('bob@x.com')!;
    expect(u.status).toBe('active');
    expect(repo.verifyLocalPassword(u.id, 'bobpw1234')).toBe(true);
  });

  it('POST rejects a duplicate email (409)', async () => {
    const agent = await adminAgent();
    await agent.post('/api/admin/users').send({ email: 'dup@x.com', password: 'pw123456' });
    const res = await agent.post('/api/admin/users').send({ email: 'dup@x.com', password: 'other123' });
    expect(res.status).toBe(409);
  });

  it('POST .../password resets the password', async () => {
    const agent = await adminAgent();
    const u = repo.createLocalUser({ email: 'carol@x.com', password: 'oldpw1234', role: 'user', status: 'active' });
    const res = await agent.post(`/api/admin/users/${u.id}/password`).send({ password: 'newpw1234' });
    expect(res.status).toBe(204);
    expect(repo.verifyLocalPassword(u.id, 'oldpw1234')).toBe(false);
    expect(repo.verifyLocalPassword(u.id, 'newpw1234')).toBe(true);
  });

  it('DELETE removes the user AND their on-disk folder', async () => {
    const agent = await adminAgent();
    const u = repo.createLocalUser({ email: 'dave@x.com', password: 'davepw12', role: 'user', status: 'active' });
    const dir = ensureUserFolder(userFolderRoot, u.id);
    writeFileSync(join(dir, 'notes', 'secret.md'), 'data');
    expect(existsSync(dir)).toBe(true);

    const res = await agent.delete(`/api/admin/users/${u.id}`);
    expect(res.status).toBe(204);
    expect(repo.getUserById(u.id)).toBeNull();
    expect(existsSync(dir)).toBe(false); // folder gone, no orphan
  });

  it('DELETE refuses the local/system user (400) and preserves it + its folder', async () => {
    const agent = await adminAgent();
    const dir = ensureUserFolder(userFolderRoot, 'local');
    const res = await agent.delete('/api/admin/users/local');
    expect(res.status).toBe(400);
    expect(repo.getUserById('local')).not.toBeNull();
    expect(existsSync(dir)).toBe(true);
  });

  it('mutations require an admin session (401/403 without login)', async () => {
    const res = await request(app).delete('/api/admin/users/whoever');
    expect([401, 403]).toContain(res.status);
  });

  // ── self-service password change ──────────────────────────────

  it('POST /api/auth/password changes own password with the correct current one', async () => {
    const agent = await adminAgent(); // logged in as the local admin (pw adminpw12)
    const res = await agent.post('/api/auth/password').send({ currentPassword: 'adminpw12', newPassword: 'brandnewpw1' });
    expect(res.status).toBe(204);
    expect(repo.verifyLocalPassword('local', 'brandnewpw1')).toBe(true);
    // restore so later assumptions about the admin password hold
    repo.setLocalPassword('local', 'adminpw12');
  });

  it('POST /api/auth/password rejects a wrong current password (403)', async () => {
    const agent = await adminAgent();
    const res = await agent.post('/api/auth/password').send({ currentPassword: 'WRONG', newPassword: 'whatever123' });
    expect(res.status).toBe(403);
    expect(repo.verifyLocalPassword('local', 'adminpw12')).toBe(true); // unchanged
  });

  it('POST /api/auth/password rejects a too-short new password (400)', async () => {
    const agent = await adminAgent();
    const res = await agent.post('/api/auth/password').send({ currentPassword: 'adminpw12', newPassword: 'short' });
    expect(res.status).toBe(400);
  });

  it('POST /api/auth/password requires authentication (401/403)', async () => {
    const res = await request(app).post('/api/auth/password').send({ currentPassword: 'x', newPassword: 'yyyyyyyy' });
    expect([401, 403]).toContain(res.status);
  });
});
