import crypto from 'crypto';
import { prisma } from '../../config/database';
import { logger } from '../../utils/logger';
import { AppError } from '../../middleware/error-handler';
import { notificationService } from './notification.service';
import { achievementsService } from './achievements.service';

function generateCode(): string {
  return crypto.randomBytes(6).toString('base64url').slice(0, 8).toUpperCase();
}

export const referralService = {
  async createInviteCode(
    userId: string,
    opts: { maxUses?: number; expiresInDays?: number; note?: string },
  ) {
    const code = generateCode();
    const expiresAt = opts.expiresInDays
      ? new Date(Date.now() + opts.expiresInDays * 24 * 60 * 60 * 1000)
      : null;

    return prisma.inviteCode.create({
      data: {
        code,
        createdByUserId: userId,
        maxUses: opts.maxUses ?? 0,
        expiresAt,
        note: opts.note,
      },
    });
  },

  async listMyCodes(userId: string, page: number, limit: number) {
    const skip = (page - 1) * limit;
    const [codes, total] = await Promise.all([
      prisma.inviteCode.findMany({
        where: { createdByUserId: userId },
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
        include: { _count: { select: { referrals: true } } },
      }),
      prisma.inviteCode.count({ where: { createdByUserId: userId } }),
    ]);

    return {
      codes,
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    };
  },

  async deactivateCode(userId: string, codeId: string) {
    const code = await prisma.inviteCode.findUnique({ where: { id: codeId } });
    if (!code) throw new AppError(404, 'Invite code not found', 'CODE_NOT_FOUND');
    if (code.createdByUserId !== userId) throw new AppError(403, 'Not your invite code', 'FORBIDDEN');

    return prisma.inviteCode.update({
      where: { id: codeId },
      data: { isActive: false },
    });
  },

  async validateCode(code: string): Promise<{ valid: boolean; referrerId?: string; error?: string }> {
    const inviteCode = await prisma.inviteCode.findUnique({ where: { code } });
    if (!inviteCode) return { valid: false, error: 'Code not found' };
    if (!inviteCode.isActive) return { valid: false, error: 'Code is no longer active' };
    if (inviteCode.expiresAt && inviteCode.expiresAt < new Date()) {
      return { valid: false, error: 'Code has expired' };
    }
    if (inviteCode.maxUses > 0 && inviteCode.usedCount >= inviteCode.maxUses) {
      return { valid: false, error: 'Code has reached maximum uses' };
    }
    return { valid: true, referrerId: inviteCode.createdByUserId };
  },

  async processRegistrationReferral(newUserId: string, inviteCode?: string) {
    if (!inviteCode) return;

    const validation = await this.validateCode(inviteCode);
    if (!validation.valid || !validation.referrerId) {
      logger.warn(`Invalid invite code "${inviteCode}" used during registration for user ${newUserId}`);
      return;
    }

    const codeRecord = await prisma.inviteCode.findUnique({ where: { code: inviteCode } });
    if (!codeRecord) return;

    // Check if user was already referred (unique constraint on referredUserId)
    const existing = await prisma.referral.findUnique({ where: { referredUserId: newUserId } });
    if (existing) return;

    await prisma.inviteCode.update({
      where: { id: codeRecord.id },
      data: { usedCount: { increment: 1 } },
    });

    await prisma.referral.create({
      data: {
        referrerId: validation.referrerId,
        referredUserId: newUserId,
        inviteCodeId: codeRecord.id,
        referralSource: 'invite_code',
      },
    });

    // Notify the referrer
    const newUser = await prisma.user.findUnique({
      where: { id: newUserId },
      select: { name: true, email: true },
    });
    const displayName = newUser?.name || newUser?.email || 'Someone';

    await notificationService.createNotification(
      validation.referrerId,
      'referral_completed',
      'New Referral!',
      `${displayName} joined using your invite code`,
      { referredUserId: newUserId, inviteCode },
    );

    // Check social achievements
    achievementsService.checkAndUnlock(validation.referrerId, ['social']).catch((err) => {
      logger.warn('Referral achievement check failed:', err);
    });
  },

  async getMyReferrals(userId: string, page: number, limit: number) {
    const skip = (page - 1) * limit;
    const [referrals, total] = await Promise.all([
      prisma.referral.findMany({
        where: { referrerId: userId },
        orderBy: { completedAt: 'desc' },
        skip,
        take: limit,
        include: {
          referredUser: { select: { id: true, name: true, email: true } },
        },
      }),
      prisma.referral.count({ where: { referrerId: userId } }),
    ]);

    return {
      referrals,
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    };
  },

  async getReferralStats(userId: string) {
    const now = new Date();
    const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);

    const [totalReferrals, thisMonth] = await Promise.all([
      prisma.referral.count({ where: { referrerId: userId } }),
      prisma.referral.count({
        where: { referrerId: userId, completedAt: { gte: firstOfMonth } },
      }),
    ]);

    return { totalReferrals, thisMonth };
  },

  async listAllReferrals(page: number, limit: number) {
    const skip = (page - 1) * limit;
    const [referrals, total] = await Promise.all([
      prisma.referral.findMany({
        orderBy: { completedAt: 'desc' },
        skip,
        take: limit,
        include: {
          referrer: { select: { id: true, name: true, email: true } },
          referredUser: { select: { id: true, name: true, email: true } },
        },
      }),
      prisma.referral.count(),
    ]);

    return {
      referrals,
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    };
  },

  async getReferralLeaderboard(limit: number = 10) {
    const results = await prisma.$queryRaw<{ referrer_id: string; count: bigint }[]>`
      SELECT referrer_id, COUNT(*) as count
      FROM referrals
      GROUP BY referrer_id
      ORDER BY count DESC
      LIMIT ${limit}
    `;

    if (results.length === 0) return [];

    const userIds = results.map((r) => r.referrer_id);
    const users = await prisma.user.findMany({
      where: { id: { in: userIds } },
      select: { id: true, name: true, email: true },
    });
    const userMap = new Map(users.map((u) => [u.id, u]));

    return results.map((r, i) => ({
      rank: i + 1,
      userId: r.referrer_id,
      name: userMap.get(r.referrer_id)?.name || null,
      email: userMap.get(r.referrer_id)?.email || '',
      referralCount: Number(r.count),
    }));
  },
};
