import { Hono, type Context } from 'hono';
import { z } from 'zod';

import { projectRateLimit } from '../middleware/rate-limit.js';
import { requireAuth } from '../middleware/session.js';
import type { AppEnv } from '../types/app-env.js';
import { assertProjectRole } from '../services/access.js';
import { audit, hashIp } from '../services/audit.js';
import {
  addMemberByEmail,
  listMembers,
  removeMember,
  updateMemberRole,
} from '../services/members.js';
import { memberRole } from '../db/schema.js';

const roleSchema = z.enum(memberRole);

const addMemberSchema = z.object({
  email: z.string().email().max(320),
  role: roleSchema,
});

const updateMemberSchema = z.object({
  role: roleSchema,
});

function ipHash(c: Context<AppEnv>): string | null {
  const fwd = c.req.raw.headers.get('x-forwarded-for');
  const ip = fwd ? fwd.split(',')[0]!.trim() : null;
  return hashIp(ip);
}

export const membersRouter = new Hono<AppEnv>();

membersRouter.use('/v1/projects/:id/members', requireAuth());
membersRouter.use('/v1/projects/:id/members/*', requireAuth());

membersRouter.get('/v1/projects/:id/members', async (c) => {
  const user = c.get('user')!;
  const { project } = await assertProjectRole(c.req.param('id'), user.id, 'viewer');
  const rows = await listMembers(project.id);
  return c.json({ members: rows });
});

membersRouter.post('/v1/projects/:id/members', projectRateLimit('mutate'), async (c) => {
  const user = c.get('user')!;
  const { project } = await assertProjectRole(c.req.param('id'), user.id, 'admin');

  const body = await c.req.json().catch(() => null);
  const parsed = addMemberSchema.safeParse(body);
  if (!parsed.success) {
    return c.json(
      { code: 'validation_failed', message: 'invalid request body', issues: parsed.error.issues },
      400,
    );
  }

  const member = await addMemberByEmail({
    projectId: project.id,
    email: parsed.data.email,
    role: parsed.data.role,
  });

  await audit({
    actorId: user.id,
    projectId: project.id,
    action: 'member.add',
    ipHash: ipHash(c),
    userAgent: c.req.header('user-agent') ?? null,
    // Per CLAUDE.md §5.1 we do not log email addresses; record the target
    // user id instead.
    metadata: { userId: member.userId, role: member.role },
  });

  return c.json({ member }, 201);
});

membersRouter.patch('/v1/projects/:id/members/:userId', projectRateLimit('mutate'), async (c) => {
  const user = c.get('user')!;
  const { project } = await assertProjectRole(c.req.param('id'), user.id, 'admin');

  const body = await c.req.json().catch(() => null);
  const parsed = updateMemberSchema.safeParse(body);
  if (!parsed.success) {
    return c.json(
      { code: 'validation_failed', message: 'invalid request body', issues: parsed.error.issues },
      400,
    );
  }

  const targetId = c.req.param('userId');
  const member = await updateMemberRole(project.id, targetId, parsed.data.role);

  await audit({
    actorId: user.id,
    projectId: project.id,
    action: 'member.update_role',
    ipHash: ipHash(c),
    userAgent: c.req.header('user-agent') ?? null,
    metadata: { userId: targetId, role: parsed.data.role },
  });

  return c.json({ member });
});

membersRouter.delete('/v1/projects/:id/members/:userId', projectRateLimit('mutate'), async (c) => {
  const user = c.get('user')!;
  const { project } = await assertProjectRole(c.req.param('id'), user.id, 'admin');

  const targetId = c.req.param('userId');
  await removeMember(project.id, targetId);

  await audit({
    actorId: user.id,
    projectId: project.id,
    action: 'member.remove',
    ipHash: ipHash(c),
    userAgent: c.req.header('user-agent') ?? null,
    metadata: { userId: targetId },
  });

  return c.json({ removed: targetId });
});
