import bcrypt from 'bcryptjs';
import { randomBytes } from 'node:crypto';
import { UserCreatedVia, UserStatus } from '@prisma/client';
import { prisma } from '../../config/database';
import { logger } from '../../utils/logger';
import { env } from '../../config/env';
import { siteSettingsService } from '../settings/settings.service';
import { rocketchatClient } from '../../services/rocketchat.client';
import type { BridgeProvisionUserInput } from './identity-bridge.schemas';

const PROVENANCE_KEY = '_jsnProvenance';

async function generateRandomPassword(): Promise<string> {
  return bcrypt.hash(randomBytes(32).toString('hex'), 12);
}

// Derive an RC-suitable username from an email. RC requires lowercase
// alphanumerics + . _ -. Truncate to keep collision rate manageable.
function deriveRcUsername(email: string): string {
  const base = email.split('@')[0]?.toLowerCase().replace(/[^a-z0-9._-]/g, '') ?? '';
  const cleaned = base.length > 0 ? base : 'jsn-supporter';
  return `${cleaned}-${randomBytes(3).toString('hex')}`.slice(0, 32);
}

// Fire-and-forget RC account provisioning after a JSN-bridged cmlite user
// is created. Failures only warn — RC outages must not block the bridge
// response or supporter signup. Stored RC id powers the chat-token bridge.
async function provisionRocketChatAccount(
  userId: string,
  email: string,
  name: string | null,
): Promise<void> {
  try {
    logger.debug('RC provisioning: starting', { userId, email });
    const settings = await siteSettingsService.get();
    if (!settings.enableChat) {
      logger.debug('RC provisioning: chat disabled, skipping', { userId });
      return;
    }
    const rcUser = await rocketchatClient.createUser({
      email,
      name: name ?? email.split('@')[0] ?? 'JSN Supporter',
      username: deriveRcUsername(email),
      // Never used — JSN issues RC tokens via admin-side createUserToken (SSO).
      password: randomBytes(24).toString('base64url'),
      roles: ['user'],
    });
    await prisma.user.update({
      where: { id: userId },
      data: { rocketChatUserId: rcUser._id },
    });
    // Belt-and-braces: explicitly invite to the supporters channel. RC's
    // default-channel flag also catches new users on createUser, but this
    // guarantees membership even if the default-channel set changes later or
    // if the user was created before the flag was flipped (e.g. backfill).
    try {
      await rocketchatClient.inviteUserToChannel(rcUser._id, env.RC_SUPPORTERS_CHANNEL);
    } catch (inviteErr) {
      logger.warn('RC supporters-channel invite failed (non-blocking)', {
        userId,
        rcUserId: rcUser._id,
        channel: env.RC_SUPPORTERS_CHANNEL,
        err: inviteErr instanceof Error ? inviteErr.message : String(inviteErr),
      });
    }
    logger.info('RC account provisioned for JSN-bridged user', {
      userId,
      rcUserId: rcUser._id,
      invitedToChannel: env.RC_SUPPORTERS_CHANNEL,
    });
  } catch (err) {
    logger.warn('RC provisioning failed (non-blocking)', {
      userId,
      email,
      err: err instanceof Error ? err.message : String(err),
      stack: err instanceof Error ? err.stack : undefined,
    });
  }
}

export const identityBridgeService = {
  async provisionFromJsn(input: BridgeProvisionUserInput): Promise<{
    userId: string;
    created: boolean;
  }> {
    const email = input.email.trim().toLowerCase();

    const existing = await prisma.user.findUnique({
      where: { email },
      select: { id: true, permissions: true },
    });

    if (existing) {
      if (input.provenance) {
        const perms = (existing.permissions as Record<string, unknown> | null) ?? {};
        if (perms[PROVENANCE_KEY] == null) {
          await prisma.user.update({
            where: { id: existing.id },
            data: { permissions: { ...perms, [PROVENANCE_KEY]: input.provenance } },
          });
        }
      }
      return { userId: existing.id, created: false };
    }

    const hashedPassword = await generateRandomPassword();
    const permissions: Record<string, unknown> = {};
    if (input.provenance) permissions[PROVENANCE_KEY] = input.provenance;

    const user = await prisma.user.create({
      data: {
        email,
        password: hashedPassword,
        name: input.displayName ?? null,
        status: UserStatus.ACTIVE,
        emailVerified: true,
        createdVia: UserCreatedVia.JSN_BRIDGE,
        permissions: Object.keys(permissions).length > 0 ? permissions : undefined,
      },
      select: { id: true },
    });

    logger.info('Provisioned user from JSN bridge', {
      userId: user.id,
      email,
      provenance: input.provenance ?? null,
    });

    // Fire-and-forget RC account provisioning. Failures here must not block
    // the bridge call (caller is JSN's fire-and-forget /auth/verify path).
    void provisionRocketChatAccount(user.id, email, input.displayName ?? null);

    return { userId: user.id, created: true };
  },
};
