import { afterEach, describe, expect, it, vi } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import { registerSecurityRoutes } from './security.routes.js';
import type { SecurityRoutesDeps } from '../index.js';

interface QueryResult {
  rows: unknown[];
}

/** F-154: mock pg.Pool returning configurable rows keyed by SQL family. */
function mockPool(countRows: unknown[], selectRows: unknown[]) {
  const query = vi.fn(async (sql: string, _params: unknown[]): Promise<QueryResult> => {
    if (String(sql).includes('COUNT')) return { rows: countRows };
    return { rows: selectRows };
  });
  const pool = { query } as unknown as import('pg').Pool;
  return { pool, query };
}

const created: FastifyInstance[] = [];

/** Builds a minimal Fastify app with security routes + mocked deps (no DB). */
async function buildAdminApp(pool: unknown) {
  const app = Fastify();
  created.push(app);
  const deps = {
    pool,
    authenticate: vi.fn().mockResolvedValue({ id: 'a1', role: 'admin' }),
    rateLimiter: vi.fn(),
    auditLogger: vi.fn(),
  } as unknown as SecurityRoutesDeps;
  await registerSecurityRoutes(app, deps);
  await app.ready();
  const query = (pool as { query: ReturnType<typeof vi.fn> }).query;
  return { app, query };
}

const ADMIN_USER = {
  id: 'a1',
  email: 'ana@example.com',
  role: 'admin',
  created_at: new Date('2026-01-01T00:00:00Z'),
};
const EDITOR_USER = {
  id: 'e1',
  email: 'e@example.com',
  role: 'editor',
  created_at: new Date('2026-01-02T00:00:00Z'),
};

afterEach(async () => {
  for (const app of created) {
    try {
      await app.close();
    } catch {
      // ignore
    }
  }
  created.length = 0;
});

describe('GET /admin/users — F-154 internal-only by default', () => {
  it('returns only internal/backoffice users and never storefront customers', async () => {
    const { app, query } = await buildAdminApp(
      mockPool([{ count: '1' }], [ADMIN_USER, EDITOR_USER]),
    );
    const res = await app.inject({ method: 'GET', url: '/admin/users' });

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body ?? '') as {
      items: Array<{ id: string; role: string }>;
      total: number;
    };
    expect(body.items).toHaveLength(2);
    expect(body.items.every((u) => u.role !== 'customer')).toBe(true);
    expect(body.items.map((u) => u.id)).toEqual(['a1', 'e1']);
    expect(body.total).toBe(1);

    const selectSql = query.mock.calls[1]![0] as string;
    expect(selectSql).toContain("role <> 'customer'");
  });

  it('narrows with ?role=admin within staff (parametrized)', async () => {
    const { app, query } = await buildAdminApp(mockPool([{ count: '1' }], [ADMIN_USER]));
    const res = await app.inject({ method: 'GET', url: '/admin/users?role=admin' });

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body ?? '') as { items: Array<{ id: string }> };
    expect(body.items).toHaveLength(1);
    expect(body.items[0]!.id).toBe('a1');

    const selectSql = query.mock.calls[1]![0] as string;
    expect(selectSql).toContain("role <> 'customer'");
    expect(selectSql).toContain('role = $1');
    expect(query.mock.calls[1]![1]).toContain('admin');
  });

  it('yields an empty set (never customers) when ?role=customer is requested', async () => {
    const { app, query } = await buildAdminApp(mockPool([{ count: '0' }], []));
    const res = await app.inject({ method: 'GET', url: '/admin/users?role=customer' });

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body ?? '') as { items: Array<{ id: string }> };
    expect(body.items).toHaveLength(0);

    const selectSql = query.mock.calls[1]![0] as string;
    expect(selectSql).toContain("role <> 'customer'");
    expect(selectSql).toContain('role = $1');
  });
});
