import crypto from 'crypto';
import { prisma } from '../lib/prisma';
import { AppError } from '../middleware/error-handler';

const CODE_LENGTH = 8; // e.g., "A3X7-K9M2"
const DEFAULT_EXPIRY_HOURS = 24;

function generateCode(): string {
  const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // no I,O,0,1 to avoid confusion
  const bytes = crypto.randomBytes(CODE_LENGTH);
  let code = '';
  for (let i = 0; i < CODE_LENGTH; i++) {
    code += chars[bytes[i] % chars.length];
  }
  // Format as XXXX-XXXX
  return `${code.slice(0, 4)}-${code.slice(4)}`;
}

/**
 * Generate a single-use invite code for agent registration.
 */
export async function createInviteCode(userId: string, expiryHours = DEFAULT_EXPIRY_HOURS) {
  const expiresAt = new Date();
  expiresAt.setHours(expiresAt.getHours() + expiryHours);

  // Retry up to 3 times in case of code collision (extremely unlikely)
  for (let attempt = 0; attempt < 3; attempt++) {
    const code = generateCode();
    try {
      return await prisma.agentInviteCode.create({
        data: {
          code,
          createdById: userId,
          expiresAt,
        },
      });
    } catch (err: unknown) {
      const prismaError = err as { code?: string };
      if (prismaError.code === 'P2002' && attempt < 2) continue; // unique constraint, retry
      throw err;
    }
  }

  throw new AppError(500, 'Failed to generate unique invite code');
}

/**
 * Validate an invite code. Returns the code record if valid.
 * Throws if expired, already used, or not found.
 */
export async function validateInviteCode(code: string) {
  const normalized = code.toUpperCase().trim();
  const invite = await prisma.agentInviteCode.findUnique({
    where: { code: normalized },
  });

  if (!invite) {
    throw new AppError(404, 'Invalid invite code', 'INVALID_CODE');
  }

  if (invite.usedAt) {
    throw new AppError(400, 'Invite code has already been used', 'CODE_USED');
  }

  if (invite.expiresAt < new Date()) {
    throw new AppError(400, 'Invite code has expired', 'CODE_EXPIRED');
  }

  return invite;
}

/**
 * Mark an invite code as used by an instance.
 */
export async function markCodeUsed(code: string, instanceId: string) {
  const normalized = code.toUpperCase().trim();
  await prisma.agentInviteCode.update({
    where: { code: normalized },
    data: {
      usedAt: new Date(),
      usedById: instanceId,
    },
  });
}

/**
 * List all invite codes with optional filtering.
 */
export async function listInviteCodes(page = 1, limit = 50) {
  const skip = (page - 1) * limit;

  const [data, total] = await Promise.all([
    prisma.agentInviteCode.findMany({
      skip,
      take: limit,
      orderBy: { createdAt: 'desc' },
      include: {
        createdBy: { select: { id: true, name: true, email: true } },
      },
    }),
    prisma.agentInviteCode.count(),
  ]);

  return { data, total, page, limit };
}

/**
 * Revoke (delete) an unused invite code.
 */
export async function revokeInviteCode(codeId: string) {
  const invite = await prisma.agentInviteCode.findUnique({ where: { id: codeId } });

  if (!invite) {
    throw new AppError(404, 'Invite code not found');
  }

  if (invite.usedAt) {
    throw new AppError(400, 'Cannot revoke a code that has already been used');
  }

  await prisma.agentInviteCode.delete({ where: { id: codeId } });
}
