import { Prisma } from '@prisma/client';
import type { PrismaService } from '../prisma/prisma.service';

const INVITE_CODE_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
const INVITE_CODE_LENGTH = 8;

export const INVITE_STATUS_ACTIVE = 'ACTIVE';
export const INVITE_STATUS_REVOKED = 'REVOKED';

export function normalizeInviteCode(input: string): string {
  return input.trim().toUpperCase();
}

export function generateInviteCodeCandidate(): string {
  let code = '';
  for (let i = 0; i < INVITE_CODE_LENGTH; i++) {
    code += INVITE_CODE_CHARS[Math.floor(Math.random() * INVITE_CODE_CHARS.length)];
  }
  return code;
}

type InviteCodeDb = Pick<PrismaService, 'user' | 'userInvite'>;

function isInviteCodeUniqueViolation(err: unknown): boolean {
  return err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002';
}

async function isCodeTaken(db: InviteCodeDb, code: string): Promise<boolean> {
  const [userHit, inviteHit] = await Promise.all([
    db.user.findUnique({ where: { inviteCode: code }, select: { id: true } }),
    db.userInvite.findUnique({ where: { code }, select: { id: true } }),
  ]);
  return Boolean(userHit || inviteHit);
}

export async function generateUniqueInviteCode(
  db: InviteCodeDb,
  maxAttempts = 12,
): Promise<string> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const code = generateInviteCodeCandidate();
    if (!(await isCodeTaken(db, code))) return code;
  }
  throw new Error('Failed to generate unique invite code');
}

/** Create history record and set as the user's current invite code. */
export async function assignInviteCodeWithHistory(
  db: InviteCodeDb,
  sponsorId: bigint,
  cashbackRate?: Prisma.Decimal | null,
): Promise<string> {
  for (let attempt = 0; attempt < 12; attempt++) {
    const candidate = generateInviteCodeCandidate();
    if (await isCodeTaken(db, candidate)) continue;
    try {
      await db.userInvite.create({
        data: {
          code: candidate,
          sponsorId,
          status: INVITE_STATUS_ACTIVE,
          ...(cashbackRate != null ? { cashbackRate } : {}),
        },
      });
      await db.user.update({
        where: { id: sponsorId },
        data: { inviteCode: candidate },
      });
      return candidate;
    } catch (err) {
      if (!isInviteCodeUniqueViolation(err)) throw err;
    }
  }
  throw new Error('Failed to assign invite code');
}

/** Revoke active codes and assign a new one with history. */
export async function rotateUserInviteCode(
  db: InviteCodeDb,
  userId: bigint,
  cashbackRate?: Prisma.Decimal | null,
): Promise<string> {
  await db.userInvite.updateMany({
    where: { sponsorId: userId, status: INVITE_STATUS_ACTIVE },
    data: { status: INVITE_STATUS_REVOKED, revokedAt: new Date() },
  });
  return assignInviteCodeWithHistory(db, userId, cashbackRate);
}

/** Assign a stable invite code once per staff user (admin/agent). */
export async function ensureUserInviteCode(db: InviteCodeDb, userId: bigint): Promise<string> {
  const existing = await db.user.findUnique({
    where: { id: userId },
    select: { inviteCode: true },
  });
  if (existing?.inviteCode) {
    const history = await db.userInvite.findUnique({
      where: { code: existing.inviteCode },
      select: { id: true },
    });
    if (!history) {
      await db.userInvite.create({
        data: {
          code: existing.inviteCode,
          sponsorId: userId,
          status: INVITE_STATUS_ACTIVE,
        },
      });
    }
    return existing.inviteCode;
  }

  return assignInviteCodeWithHistory(db, userId);
}
