import type { FastifyInstance } from "fastify";
import bcrypt from "bcryptjs";
import { z } from "zod";
import { requireSession } from "../../lib/auth.js";
import { writeAuditLog } from "../../lib/audit.js";
import { createId } from "../../lib/id.js";
import { prisma } from "../../lib/prisma.js";
import { getSessionProfile } from "../../lib/session.js";
import {
  buildTemporaryPassword,
  ensureLawFirmTeamUser,
  ensureWorkspaceMembership,
  splitName,
} from "../../lib/user-onboarding.js";

const userCreateSchema = z.object({
  name: z.string().min(2).max(150),
  email: z.string().email(),
  phone: z.string().trim().max(50).optional().or(z.literal("")),
});

const userParamsSchema = z.object({
  userId: z.string().uuid(),
});

const userUpdateSchema = z.object({
  name: z.string().min(2).max(150),
  email: z.string().email(),
  phone: z.string().trim().max(50).optional().or(z.literal("")),
  isActive: z.boolean(),
});

export async function registerUserRoutes(app: FastifyInstance) {
  app.get("/roles", async (_request, _reply) => {
    const roles = await prisma.role.findMany({
      orderBy: {
        name: "asc",
      },
    });

    return roles.map((role) => ({
      code: role.code,
      name: role.name,
      description: role.description,
    }));
  });

  app.get("/", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const workspaceUserRows = await prisma.$queryRaw<Array<{ user_id: string }>>`
      SELECT wm.user_id
      FROM workspace_memberships wm
      INNER JOIN users u ON u.id = wm.user_id
      WHERE wm.law_firm_id = ${profile.lawFirm.id}
        AND u.deleted_at IS NULL
      ORDER BY u.created_at ASC
    `;
    const workspaceUserIds = workspaceUserRows.map((row) => row.user_id);
    const [users, userRoles] = workspaceUserIds.length
      ? await prisma.$transaction([
          prisma.user.findMany({
            where: {
              id: {
                in: workspaceUserIds,
              },
              deleted_at: null,
            },
            orderBy: {
              created_at: "asc",
            },
          }),
          prisma.userRole.findMany({
            where: {
              user_id: {
                in: workspaceUserIds,
              },
            },
          }),
        ])
      : [[], []];

    const rolesByUser = new Map<string, string[]>();
    for (const userRole of userRoles) {
      const existing = rolesByUser.get(userRole.user_id) ?? [];
      existing.push(userRole.role_code);
      rolesByUser.set(userRole.user_id, existing);
    }

    return users.map((user) => ({
      id: user.id,
      email: user.email,
      phone: user.phone,
      displayName: user.display_name ?? `${user.first_name} ${user.last_name}`.trim(),
      isActive: user.is_active,
      roleCodes: rolesByUser.get(user.id) ?? [],
      createdAt: user.created_at,
    }));
  });

  app.post("/", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    if (!profile.user.roleCodes.includes("admin")) {
      throw reply.forbidden("Only admins can create users");
    }

    const payload = userCreateSchema.parse(request.body);
    const temporaryPassword = buildTemporaryPassword();
    const passwordHash = await bcrypt.hash(temporaryPassword, 12);
    const name = splitName(payload.name);
    const normalizedPhone = String(payload.phone ?? "").trim() || null;
    const defaultRoleCode = "attorney";
    const existingUser = await prisma.user.findFirst({
      where: {
        email: payload.email,
        deleted_at: null,
      },
      select: {
        id: true,
      },
    });

    if (existingUser) {
      throw reply.conflict("This email is already in use");
    }

      const user = await prisma.$transaction(async (tx) => {
      await ensureWorkspaceMembership(tx, {
        lawFirmId: profile.lawFirm.id,
        userId: profile.user.id,
        membershipRole: "owner",
        createdByUserId: profile.user.id,
        isDefault: true,
      });

      await ensureLawFirmTeamUser(tx, {
        lawFirmId: profile.lawFirm.id,
        userId: profile.user.id,
        displayName: profile.user.displayName,
        email: profile.user.email,
        membershipRole: "owner",
        invitedByUserId: profile.user.id,
        teamName: "Time interno",
      });

      const createdUser = await tx.user.create({
        data: {
          id: createId(),
          law_firm_id: profile.lawFirm.id,
          primary_office_id: null,
          first_name: name.firstName,
          last_name: name.lastName,
          display_name: name.displayName,
          email: payload.email,
          phone: normalizedPhone,
          password_hash: passwordHash,
        },
      });

      await ensureWorkspaceMembership(tx, {
        lawFirmId: profile.lawFirm.id,
        userId: createdUser.id,
        membershipRole: "member",
        createdByUserId: profile.user.id,
        isDefault: true,
      });

      await tx.userRole.create({
        data: {
          id: createId(),
          user_id: createdUser.id,
          role_code: defaultRoleCode,
          office_id: null,
          scope_type: "law_firm",
          assigned_by_user_id: profile.user.id,
        },
      });

      await ensureLawFirmTeamUser(tx, {
        lawFirmId: profile.lawFirm.id,
        userId: createdUser.id,
        displayName: createdUser.display_name ?? name.displayName,
        email: createdUser.email,
        membershipRole: "member",
        invitedByUserId: profile.user.id,
        teamName: "Time interno",
      });

      return createdUser;
    });

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "user",
      entityId: user.id,
      action: "user.create",
      afterJson: {
        email: user.email,
        roleCode: defaultRoleCode,
        phone: user.phone,
      },
      request,
    });

    return reply.code(201).send({
      id: user.id,
      email: user.email,
      displayName: user.display_name,
      roleCode: defaultRoleCode,
      temporaryPassword,
    });
  });

  app.patch("/:userId", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    if (!profile.user.roleCodes.includes("admin")) {
      throw reply.forbidden("Only admins can update users");
    }

    const params = userParamsSchema.parse(request.params);
    const payload = userUpdateSchema.parse(request.body);
    const name = splitName(payload.name);
    const normalizedPhone = String(payload.phone ?? "").trim() || null;

    const [workspaceMember] = await prisma.$queryRaw<Array<{ user_id: string }>>`
      SELECT user_id
      FROM workspace_memberships
      WHERE law_firm_id = ${profile.lawFirm.id}
        AND user_id = ${params.userId}
      LIMIT 1
    `;

    if (!workspaceMember) {
      throw reply.notFound("User not found in this workspace");
    }

    const existingUser = await prisma.user.findFirst({
      where: {
        email: payload.email,
        deleted_at: null,
        NOT: {
          id: params.userId,
        },
      },
      select: {
        id: true,
      },
    });

    if (existingUser) {
      throw reply.conflict("This email is already in use");
    }

    const beforeUser = await prisma.user.findUnique({
      where: {
        id: params.userId,
      },
      select: {
        email: true,
        phone: true,
        display_name: true,
        first_name: true,
        last_name: true,
        is_active: true,
      },
    });

    if (!beforeUser) {
      throw reply.notFound("User not found");
    }

    const updatedUser = await prisma.user.update({
      where: {
        id: params.userId,
      },
      data: {
        first_name: name.firstName,
        last_name: name.lastName,
        display_name: name.displayName,
        email: payload.email,
        phone: normalizedPhone,
        is_active: payload.isActive,
      },
      select: {
        id: true,
        email: true,
        phone: true,
        display_name: true,
        first_name: true,
        last_name: true,
        is_active: true,
        created_at: true,
      },
    });

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "user",
      entityId: updatedUser.id,
      action: "user.update",
      beforeJson: beforeUser,
      afterJson: {
        email: updatedUser.email,
        phone: updatedUser.phone,
        displayName:
          updatedUser.display_name ??
          `${updatedUser.first_name} ${updatedUser.last_name}`.trim(),
        isActive: updatedUser.is_active,
      },
      request,
    });

    return {
      id: updatedUser.id,
      email: updatedUser.email,
      phone: updatedUser.phone,
      displayName:
        updatedUser.display_name ??
        `${updatedUser.first_name} ${updatedUser.last_name}`.trim(),
      isActive: updatedUser.is_active,
      createdAt: updatedUser.created_at,
    };
  });
}
