/**
 * AAO Gateway Phase 2a — cross-module integration scenarios.
 *
 * The unit tests cover each layer in isolation; this file exercises
 * the wiring across layers to catch regressions where (for example) an
 * admin-issued key authenticates but a config-imported one doesn't.
 *
 * Scenarios:
 *   1. Issue via Repository → authenticate via auth middleware (DB path)
 *   2. Importer adds a config key → same auth path resolves it
 *      (verifies source='config-import' rows are first-class at auth)
 *   3. Importer is idempotent: 2nd run does not duplicate or 5xx
 *   4. Rotate via admin API → new key works, old key 401s
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import express, { type Request, type RequestHandler } from 'express';
import request from 'supertest';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Repository } from '../db/repository.js';
import { buildAuthMiddleware, type DbKeyMatch } from './auth.js';
import {
  generateVirtualKey,
} from './key-format.js';
import { importConfigKeysToDb } from './config-migration.js';
import { createAdminGatewayApi } from '../bridge/admin-gateway-api.js';

function buildAdminApp(repo: Repository): express.Application {
  const app = express();
  app.use(express.json({ limit: '4kb' }));
  const guard: RequestHandler = (req, _res, next) => {
    (req as Request & { user?: unknown }).user = { id: 'admin-1', role: 'admin', status: 'active' };
    next();
  };
  app.use(
    '/api/admin/gateway/keys',
    createAdminGatewayApi({
      repo,
      requireAdmin: guard,
      getUserId: (req) => {
        const u = (req as Request & { user?: { id?: string } }).user;
        return u?.id ?? null;
      },
    }),
  );
  return app;
}

function buildAuthHarness(repo: Repository): express.Application {
  const app = express();
  const dbLookup = (keyHash: string): DbKeyMatch | null => {
    const row = repo.findGatewayVirtualKeyByHash(keyHash);
    return row
      ? { id: row.id, team: row.team, allowedModels: row.allowedModels }
      : null;
  };
  const mw = buildAuthMiddleware({ keys: [], dbLookup });
  // Tiny mock endpoint that echoes the matched team back to assert auth result.
  app.get(
    '/probe',
    mw,
    (req: Request, res) => {
      const auth = (req as Request & { gatewayAuth?: { team?: string; source?: string } }).gatewayAuth;
      res.json({ team: auth?.team, source: auth?.source });
    },
  );
  return app;
}

describe('Phase 2a integration', () => {
  let tmpDir: string;
  let repo: Repository;

  beforeEach(() => {
    tmpDir = mkdtempSync(join(tmpdir(), 'phase2a-int-'));
    repo = new Repository(join(tmpDir, 'test.db'));
  });

  afterEach(() => {
    repo.close();
    rmSync(tmpDir, { recursive: true, force: true });
  });

  it('admin issue → DB auth resolves the new key', async () => {
    const adminApp = buildAdminApp(repo);
    const authApp = buildAuthHarness(repo);
    const issued = await request(adminApp)
      .post('/api/admin/gateway/keys')
      .send({ team: 'alpha' });
    expect(issued.status).toBe(201);
    const raw = issued.body.key as string;

    const probe = await request(authApp).get('/probe').set('Authorization', `Bearer ${raw}`);
    expect(probe.status).toBe(200);
    expect(probe.body).toEqual({ team: 'alpha', source: 'db' });
  });

  it('importer adds a config-import key → same auth path resolves it', async () => {
    const raw = 'sk-config-import-secret-value-XYZ';
    const result = importConfigKeysToDb(
      { virtualKeys: [{ key: raw, team: 'beta', allowedModels: ['qwen3:8b'] }] },
      repo,
    );
    expect(result).toEqual({ imported: 1, skipped: 0, resynced: 0 });

    const authApp = buildAuthHarness(repo);
    const probe = await request(authApp).get('/probe').set('Authorization', `Bearer ${raw}`);
    expect(probe.status).toBe(200);
    expect(probe.body.team).toBe('beta');
    expect(probe.body.source).toBe('db');
  });

  it('importer is idempotent across boots', () => {
    const raw = 'sk-rerun-secret';
    importConfigKeysToDb({ virtualKeys: [{ key: raw, team: 'gamma' }] }, repo);
    const second = importConfigKeysToDb({ virtualKeys: [{ key: raw, team: 'gamma' }] }, repo);
    expect(second).toEqual({ imported: 0, skipped: 1, resynced: 0 });
    expect(repo.listGatewayVirtualKeys({ team: 'gamma' })).toHaveLength(1);
  });

  it('rotate via admin API: new key works, old key 401s', async () => {
    const adminApp = buildAdminApp(repo);
    const authApp = buildAuthHarness(repo);
    const issued = await request(adminApp)
      .post('/api/admin/gateway/keys')
      .send({ team: 'alpha', allowedModels: ['qwen3:8b'] });
    const oldRaw = issued.body.key as string;
    const id = issued.body.id as string;

    const probeBefore = await request(authApp).get('/probe').set('Authorization', `Bearer ${oldRaw}`);
    expect(probeBefore.status).toBe(200);

    const rotated = await request(adminApp).post(`/api/admin/gateway/keys/${id}/rotate`).send({});
    expect(rotated.status).toBe(201);
    const newRaw = rotated.body.key as string;
    expect(newRaw).not.toBe(oldRaw);

    const probeOld = await request(authApp).get('/probe').set('Authorization', `Bearer ${oldRaw}`);
    expect(probeOld.status).toBe(401);
    const probeNew = await request(authApp).get('/probe').set('Authorization', `Bearer ${newRaw}`);
    expect(probeNew.status).toBe(200);
    expect(probeNew.body.team).toBe('alpha');
  });

  it('hash collision sanity: random keys never share a hash in practice', () => {
    const seen = new Set<string>();
    for (let i = 0; i < 200; i++) {
      const k = generateVirtualKey();
      expect(seen.has(k.hash)).toBe(false);
      seen.add(k.hash);
    }
  });
});
