import { type Request, type Response, type NextFunction } from "express";
import { db } from "@workspace/db";
import {
  usersTable,
  userRolesTable,
  rolesTable,
  rolePermissionsTable,
  permissionsTable,
  groupRolesTable,
  userGroupsTable,
} from "@workspace/db";
import { eq, and, inArray, or } from "drizzle-orm";

/**
 * Effective roles for a user = direct user_roles ∪ roles attached to any of
 * the user's groups via group_roles. Returns distinct role names.
 */
export async function getEffectiveRoleIds(userId: number): Promise<number[]> {
  const rows = await db
    .select({ roleId: rolesTable.id })
    .from(rolesTable)
    .where(
      or(
        inArray(
          rolesTable.id,
          db
            .select({ rid: userRolesTable.roleId })
            .from(userRolesTable)
            .where(eq(userRolesTable.userId, userId)),
        ),
        inArray(
          rolesTable.id,
          db
            .select({ rid: groupRolesTable.roleId })
            .from(groupRolesTable)
            .innerJoin(
              userGroupsTable,
              eq(userGroupsTable.groupId, groupRolesTable.groupId),
            )
            .where(eq(userGroupsTable.userId, userId)),
        ),
      ),
    );
  return Array.from(new Set(rows.map((r) => r.roleId)));
}

declare module "express-session" {
  interface SessionData {
    userId: number;
  }
}

export async function requireAuth(
  req: Request,
  res: Response,
  next: NextFunction,
): Promise<void> {
  if (!req.session.userId) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  const [user] = await db
    .select()
    .from(usersTable)
    .where(eq(usersTable.id, req.session.userId));

  if (!user || !user.isActive) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  next();
}

export async function requireAdmin(
  req: Request,
  res: Response,
  next: NextFunction,
): Promise<void> {
  if (!req.session.userId) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  const [user] = await db
    .select({ isActive: usersTable.isActive })
    .from(usersTable)
    .where(eq(usersTable.id, req.session.userId));
  if (!user || !user.isActive) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  const effectiveIds = await getEffectiveRoleIds(req.session.userId);
  let isAdmin = false;
  if (effectiveIds.length > 0) {
    const adminRows = await db
      .select({ id: rolesTable.id })
      .from(rolesTable)
      .where(and(inArray(rolesTable.id, effectiveIds), eq(rolesTable.name, "admin")));
    isAdmin = adminRows.length > 0;
  }

  if (!isAdmin) {
    res.status(403).json({ error: "Forbidden" });
    return;
  }

  next();
}

export function requirePermission(name: string) {
  return async function (
    req: Request,
    res: Response,
    next: NextFunction,
  ): Promise<void> {
    if (!req.session.userId) {
      res.status(401).json({ error: "Unauthorized" });
      return;
    }

    const [user] = await db
      .select({ isActive: usersTable.isActive })
      .from(usersTable)
      .where(eq(usersTable.id, req.session.userId));
    if (!user || !user.isActive) {
      res.status(401).json({ error: "Unauthorized" });
      return;
    }

    const effectiveIds = await getEffectiveRoleIds(req.session.userId);
    if (effectiveIds.length === 0) {
      res.status(403).json({ error: "Forbidden" });
      return;
    }
    const rows = await db
      .select({ permName: permissionsTable.name })
      .from(rolePermissionsTable)
      .innerJoin(
        permissionsTable,
        eq(rolePermissionsTable.permissionId, permissionsTable.id),
      )
      .where(
        and(
          inArray(rolePermissionsTable.roleId, effectiveIds),
          eq(permissionsTable.name, name),
        ),
      );

    if (rows.length === 0) {
      res.status(403).json({ error: "Forbidden" });
      return;
    }

    next();
  };
}

export async function userHasPermission(
  userId: number,
  name: string,
): Promise<boolean> {
  const effectiveIds = await getEffectiveRoleIds(userId);
  if (effectiveIds.length === 0) return false;
  const rows = await db
    .select({ id: rolePermissionsTable.permissionId })
    .from(rolePermissionsTable)
    .innerJoin(
      permissionsTable,
      eq(rolePermissionsTable.permissionId, permissionsTable.id),
    )
    .where(
      and(
        inArray(rolePermissionsTable.roleId, effectiveIds),
        eq(permissionsTable.name, name),
      ),
    );
  return rows.length > 0;
}

export async function getUserRoles(userId: number): Promise<string[]> {
  const effectiveIds = await getEffectiveRoleIds(userId);
  if (effectiveIds.length === 0) return [];
  const roles = await db
    .select({ roleName: rolesTable.name })
    .from(rolesTable)
    .where(inArray(rolesTable.id, effectiveIds));
  return Array.from(new Set(roles.map((r) => r.roleName)));
}

const EXECUTIVE_READ_ROLES: ReadonlyArray<string> = [
  "admin",
  "executive_ceo",
  "executive_office_manager",
  "executive_coord_lead",
  "executive_coordinator",
  "executive_viewer",
];

/**
 * Gate any read access to the Executive Meetings module. Allows admin and
 * any of the executive_* roles. Mutate / approve / audit-view sub-roles
 * are layered on top via local helpers in the routes file.
 */
export async function requireExecutiveAccess(
  req: Request,
  res: Response,
  next: NextFunction,
): Promise<void> {
  if (!req.session.userId) {
    res.status(401).json({ error: "Unauthorized", code: "unauthorized" });
    return;
  }
  const [user] = await db
    .select({ isActive: usersTable.isActive })
    .from(usersTable)
    .where(eq(usersTable.id, req.session.userId));
  if (!user || !user.isActive) {
    res.status(401).json({ error: "Unauthorized", code: "unauthorized" });
    return;
  }
  const ids = await getEffectiveRoleIds(req.session.userId);
  if (ids.length === 0) {
    res.status(403).json({ error: "Forbidden", code: "forbidden" });
    return;
  }
  const rows = await db
    .select({ name: rolesTable.name })
    .from(rolesTable)
    .where(inArray(rolesTable.id, ids));
  const names = new Set(rows.map((r) => r.name));
  const ok = EXECUTIVE_READ_ROLES.some((r) => names.has(r));
  if (!ok) {
    res.status(403).json({ error: "Forbidden", code: "forbidden" });
    return;
  }
  next();
}

