import { randomBytes } from 'node:crypto';
import prisma from '../config/db.js';
import { env } from '../config/env.js';
import { ORG_ADMIN_ROLE } from '../config/access.js';
import { sendTemplateEmail } from '../utils/mailer.js';
import { roleLabel } from './session.model.js';
import { userInclude } from './tenantUser.model.js';

// Invites: a user row without a Firebase account yet, holding a single-use
// link token valid for INVITE_DAYS. Accepting creates the Firebase account.

export const INVITE_DAYS = 7;
const DAY_MS = 86_400_000;

export const newInviteToken = () => randomBytes(24).toString('base64url');
export const inviteExpiry = (invitedAt: Date) => new Date(invitedAt.getTime() + INVITE_DAYS * DAY_MS);
const displayDate = (date: Date) => new Intl.DateTimeFormat('en-IN', { dateStyle: 'medium' }).format(date);

/** "priya.nair@acme.com" → "Priya Nair", until they choose their own name. */
export const nameFromEmail = (email: string) =>
  email.split('@')[0].replace(/[._-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()).trim() || email;

export function findUsersByEmails(emails: string[]) {
  return prisma.user.findMany({ where: { email: { in: emails } }, select: { email: true, organizationId: true } });
}

export interface NewInvites {
  organizationId: number;
  invitedById: number;
  emails: string[];
  roleIds: number[];
  departmentId: number | null;
  teamId: number | null;
  projectIds: number[];
  /** Project role for the new memberships (from their organization roles). */
  projectRole: string;
}

export async function createInvitedUsers(input: NewInvites) {
  const now = new Date();
  return prisma.$transaction(
    input.emails.map((email) =>
      prisma.user.create({
        data: {
          email,
          name: nameFromEmail(email),
          organizationId: input.organizationId,
          departmentId: input.departmentId,
          teamId: input.teamId,
          invitedById: input.invitedById,
          invitedAt: now,
          inviteToken: newInviteToken(),
          tenantRoles: { create: input.roleIds.map((roleId) => ({ roleId })) },
          projectMemberships: { create: input.projectIds.map((projectId) => ({ projectId, projectRole: input.projectRole })) },
        },
        include: userInclude,
      }),
    ),
  );
}

/** New link and a fresh 7 days (the old link stops working). */
export function refreshInvite(id: number, invitedById?: number) {
  return prisma.user.update({
    where: { id },
    data: { inviteToken: newInviteToken(), invitedAt: new Date(), ...(invitedById ? { invitedById } : {}) },
    include: userInclude,
  });
}

export function findInvite(token: string) {
  return prisma.user.findUnique({
    where: { inviteToken: token },
    include: { organization: true, invitedBy: { select: { name: true } }, tenantRoles: { include: { role: true } } },
  });
}

/** An invite is usable while pending (no Firebase account), active and within 7 days. */
export function isInviteUsable(user: { firebaseUid: string | null; isActive: boolean; invitedAt: Date | null }) {
  return !user.firebaseUid && user.isActive && user.invitedAt !== null && inviteExpiry(user.invitedAt) > new Date();
}

export function acceptInvite(id: number, firebaseUid: string, name: string) {
  return prisma.user.update({
    where: { id },
    // They opened the link from their inbox, so the address is verified.
    data: { firebaseUid, name, inviteToken: null, emailVerified: true, lastLoginAt: new Date() },
  });
}

export async function sendInviteEmail(userId: number) {
  const user = await prisma.user.findUniqueOrThrow({
    where: { id: userId },
    include: { organization: true, invitedBy: { select: { name: true } }, tenantRoles: { include: { role: true } } },
  });
  if (!user.inviteToken || !user.invitedAt || !user.organization) return false;
  const organizationName = user.organization.name;
  const invitedByName = user.invitedBy?.name ?? 'Your administrator';
  return sendTemplateEmail({
    to: user.email,
    subject: `${invitedByName} invited you to ${organizationName} on BlenAxis`,
    template: env.MAILGUN_TEMPLATE_INVITE,
    variables: {
      name: user.name,
      organizationName,
      invitedByName,
      inviteUrl: `${env.TENANT_APP_URL}/invite/${user.inviteToken}`,
      expiresAt: displayDate(inviteExpiry(user.invitedAt)),
      roles: user.tenantRoles.map(({ role }) => roleLabel(role)).join(', '),
      isOrganizationAdmin: user.tenantRoles.some(({ role }) => role.name === ORG_ADMIN_ROLE),
      supportEmail: env.SUPPORT_EMAIL,
    },
  });
}

export async function sendPasswordResetEmail(user: { email: string; name: string }, organizationName: string, resetUrl: string) {
  return sendTemplateEmail({
    to: user.email,
    subject: 'Reset your BlenAxis password',
    template: env.MAILGUN_TEMPLATE_PASSWORD_RESET,
    variables: { name: user.name, organizationName, resetUrl, supportEmail: env.SUPPORT_EMAIL },
  });
}
