import type { Prisma } from '@prisma/client';
import prisma from '../config/db.js';
import { ALWAYS_ON_MODULES, ORG_ADMIN_ROLE } from '../config/access.js';
import { encodeId } from '../utils/idCodec.js';
import { fileUrl } from '../utils/storage.js';

const accessRoleInclude = { include: { permissions: { include: { permission: true } } } } as const;

const sessionInclude = {
  organization: { include: { roleModuleOverrides: true } },
  role: true,
  tenantRoles: { include: { role: accessRoleInclude } },
  department: { select: { name: true } },
  team: { select: { name: true } },
} satisfies Prisma.UserInclude;

export type SessionUserRecord = Prisma.UserGetPayload<{ include: typeof sessionInclude }>;

export function findSessionUser(id: number) {
  return prisma.user.findUnique({ where: { id }, include: sessionInclude });
}

// "site-engineer" → "Site Engineer", used when a role has no label.
const titleCase = (name: string) =>
  name.split(/[-_\s]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');

// The organization's first active Organization Admin, shown on "no access" screens.
async function tenantAdmin(organizationId: number) {
  const admin = await prisma.user.findFirst({
    where: { organizationId, isActive: true, firebaseUid: { not: null }, tenantRoles: { some: { role: { name: ORG_ADMIN_ROLE } } } },
    orderBy: { id: 'asc' },
    select: { name: true, email: true },
  });
  return admin ?? undefined;
}

type AccessRole = SessionUserRecord['tenantRoles'][number]['role'];

export const tenantRolesOf = (user: SessionUserRecord): AccessRole[] =>
  user.tenantRoles.map(({ role }) => role);

// Effective access, resolved the same way the tenant app explains it
// (services/session/effectiveAccess.ts): the union of the user's roles, where a
// permission counts only while its module is subscribed by the organization
// AND enabled for that same role. Administration and Project Core are always on.
export function effectiveAccess(user: SessionUserRecord) {
  const tenantModules = user.organization?.moduleKeys ?? [];
  // Module access for shared default roles can be changed per organization.
  const overrides = new Map((user.organization?.roleModuleOverrides ?? []).map((o) => [o.roleId, o.modules]));
  const roles = tenantRolesOf(user).map((role) => ({ ...role, modules: overrides.get(role.id) ?? role.modules }));
  const enabled = (role: AccessRole, module: string) =>
    ALWAYS_ON_MODULES.includes(module as never) || role.modules.includes(module);

  const permissions = new Set<string>();
  for (const role of roles) {
    for (const { permission } of role.permissions) {
      if (permission.module && tenantModules.includes(permission.module) && enabled(role, permission.module)) {
        permissions.add(permission.name);
      }
    }
  }
  const modules = tenantModules.filter(
    (m) => ALWAYS_ON_MODULES.includes(m as never) || roles.some((role) => role.modules.includes(m)),
  );
  // "All projects" wins: one all-projects role is enough to see every project.
  const scope: 'all' | 'assigned' = roles.some((role) => role.scope === 'all') ? 'all' : 'assigned';
  return { permissions: [...permissions], modules, scope };
}

export const roleLabel = (role: { name: string; label: string | null }) => role.label ?? titleCase(role.name);

// The body of /auth/login and /auth/me: who is signed in, what they may do,
// and the tenant they belong to (null for users not linked to an organization).
export async function buildSession(user: SessionUserRecord) {
  const { permissions, modules, scope } = effectiveAccess(user);
  const org = user.organization;
  return {
    user: {
      id: encodeId(user.id),
      name: user.name,
      email: user.email,
      roles: tenantRolesOf(user).map(roleLabel),
      permissions,
      modules,
      scope,
      // Kept for existing clients of /auth/login and /auth/me.
      role: user.role?.name ?? 'user',
      // Stored as an S3 key (or a Firebase/Google photo URL); returned as a URL to load.
      avatarUrl: await fileUrl(user.avatarUrl) ?? null,
      emailVerified: user.emailVerified,
      // Shown and edited on My profile.
      phone: user.phone ?? undefined,
      jobTitle: user.jobTitle ?? undefined,
      department: user.department?.name,
      team: user.team?.name,
    },
    tenant: org
      ? {
          id: encodeId(org.id),
          name: org.name,
          slug: org.code,
          moduleKeys: org.moduleKeys,
          // Shown in the tenant app's sidebar (an S3 key or an older URL, returned as a URL).
          logoUrl: await fileUrl(org.logoUrl) ?? null,
          admin: await tenantAdmin(org.id),
        }
      : null,
  };
}
