import type { FastifyInstance } from 'fastify';
import type { AuthService } from '../services/auth.service.js';
import type { UserService } from '../services/user.service.js';
import type { GroupService } from '../services/group.service.js';
import type { RbacDefinitionService } from '../services/rbac-definition.service.js';
import type { RbacService } from '../services/rbac.service.js';
import { createAuthMiddleware } from '../middleware/auth.js';
import { createRbacMiddleware } from '../middleware/rbac.js';
import { grantSelfPasswordPermission } from '../bootstrap/self-password-permission.js';

export interface AuthRouteDeps {
  authService: AuthService;
  userService: UserService;
  groupService: GroupService;
  rbacDefinitionService: RbacDefinitionService;
  rbacService: RbacService;
}

export function registerAuthRoutes(app: FastifyInstance, deps: AuthRouteDeps): void {
  const authMiddleware = createAuthMiddleware({
    findSession: (token) => deps.authService.findSession(token),
  });
  const { requireOperation } = createRbacMiddleware(deps.rbacService);

  // GET /api/v1/auth/status — unauthenticated, returns whether any users exist
  app.get('/api/v1/auth/status', async () => {
    const count = await deps.userService.count();
    return { hasUsers: count > 0 };
  });

  // POST /api/v1/auth/bootstrap — only works when no users exist (first-run setup)
  app.post('/api/v1/auth/bootstrap', async (request, reply) => {
    const count = await deps.userService.count();
    if (count > 0) {
      return reply.code(409).send({ error: 'Users already exist. Use login instead.' });
    }

    const { email, password, name } = request.body as { email: string; password: string; name?: string };

    // Create the first admin user
    const adminUser = await deps.userService.create({
      email,
      password,
      ...(name !== undefined ? { name } : {}),
    });

    // Fresh install: the admin is also a user, so give them the default
    // self-service password-change permission (default-on; see
    // grantSelfPasswordPermission). Their bootstrap-admin edit:* already
    // covers resetting OTHER users.
    await grantSelfPasswordPermission(deps.rbacDefinitionService, adminUser);

    // Create "admin" group and add the first user to it
    await deps.groupService.create({
      name: 'admin',
      description: 'Bootstrap admin group',
      members: [email],
    });

    // Create bootstrap RBAC: full resource access + all operations
    await deps.rbacDefinitionService.create({
      name: 'bootstrap-admin',
      subjects: [{ kind: 'Group', name: 'admin' }],
      roleBindings: [
        { role: 'edit', resource: '*' },
        { role: 'run', resource: '*' },
        { role: 'run', action: 'impersonate' },
        { role: 'run', action: 'logs' },
        { role: 'run', action: 'backup' },
        { role: 'run', action: 'restore' },
        { role: 'run', action: 'audit-purge' },
      ],
    });

    // Auto-login so the caller gets a token immediately
    const session = await deps.authService.login(email, password);
    reply.code(201);
    return session;
  });

  // GET /api/v1/auth/me — returns current user identity.
  // The authMiddleware guards this route, but if it ever falls through with
  // `request.userId === undefined` (e.g. an McpToken bearer that authenticated
  // a service principal but has no associated User row), Prisma blows up on
  // findUnique({ where: { id: undefined } }) with PrismaClientValidationError
  // — surface a clear 401 instead.
  app.get('/api/v1/auth/me', { preHandler: [authMiddleware] }, async (request, reply) => {
    if (request.userId === undefined) {
      reply.code(401);
      return { error: 'No user identity on this request (service-account or token-bound principal cannot be queried via /me)' };
    }
    const user = await deps.userService.getById(request.userId);
    return { id: user.id, email: user.email, name: user.name ?? null };
  });

  // POST /api/v1/auth/login — no auth required
  app.post<{
    Body: { email: string; password: string };
  }>('/api/v1/auth/login', async (request) => {
    const { email, password } = request.body;
    const result = await deps.authService.login(email, password);
    return result;
  });

  // POST /api/v1/auth/logout — auth required
  app.post('/api/v1/auth/logout', { preHandler: [authMiddleware] }, async (request) => {
    const header = request.headers.authorization;
    // Auth middleware already validated the header; extract the token
    const token = header!.slice(7);
    await deps.authService.logout(token);
    return { success: true };
  });

  // POST /api/v1/auth/impersonate — requires auth + run:impersonate operation
  app.post(
    '/api/v1/auth/impersonate',
    { preHandler: [authMiddleware, requireOperation('impersonate')] },
    async (request) => {
      const { email } = request.body as { email: string };
      const result = await deps.authService.impersonate(email);
      return result;
    },
  );
}
