/**
 * Engagement Scoring EventBus Listener
 *
 * Maintains a real-time engagement score for each contact based on their
 * activity across the platform. Scores are stored in Redis for fast access
 * and a sorted set provides leaderboard queries.
 *
 * Scoring weights:
 *   Donation completed     +50
 *   Subscription activated +40
 *   Product purchased      +30
 *   Shift signup           +20
 *   Canvass visit          +15
 *   Response submitted     +15
 *   Campaign email sent    +10
 *   SMS received           +10
 *   Email opened           +5
 *   Email link clicked     +8
 *   Video viewed           +3
 *
 * Redis keys:
 *   engagement:score:{contactId}   — total score (string/integer)
 *   engagement:leaderboard         — sorted set (contactId → score)
 *   engagement:last:{contactId}    — ISO timestamp of last activity
 *
 * No feature guard — always active (scores are ephemeral in Redis).
 */

import { eventBus } from '../event-bus.service';
import { logger } from '../../utils/logger';

// Lazy-import to avoid circular dependency
let redisPromise: ReturnType<typeof getRedis> | null = null;
async function getRedis() {
  const { redis } = await import('../../config/redis');
  return redis;
}
function lazyRedis() {
  if (!redisPromise) redisPromise = getRedis();
  return redisPromise;
}

let prismaPromise: ReturnType<typeof getPrisma> | null = null;
async function getPrisma() {
  const { prisma } = await import('../../config/database');
  return prisma;
}
function lazyPrisma() {
  if (!prismaPromise) prismaPromise = getPrisma();
  return prismaPromise;
}

/** Find contactId by email. Returns null if not found. */
async function findContactByEmail(email?: string | null): Promise<string | null> {
  if (!email) return null;
  try {
    const prisma = await lazyPrisma();
    const row = await prisma.contactEmail.findFirst({
      where: { email: email.toLowerCase() },
      select: { contactId: true },
    });
    return row?.contactId ?? null;
  } catch {
    return null;
  }
}

/** Find contactId by phone. Returns null if not found. */
async function findContactByPhone(phone: string): Promise<string | null> {
  try {
    const prisma = await lazyPrisma();
    const row = await prisma.contactPhone.findFirst({
      where: { phone },
      select: { contactId: true },
    });
    return row?.contactId ?? null;
  } catch {
    return null;
  }
}

/** Increment a contact's engagement score in Redis. */
async function addScore(contactId: string, points: number): Promise<void> {
  try {
    const redis = await lazyRedis();
    const pipeline = redis.pipeline();
    pipeline.incrby(`engagement:score:${contactId}`, points);
    pipeline.zincrby('engagement:leaderboard', points, contactId);
    pipeline.set(`engagement:last:${contactId}`, new Date().toISOString());
    await pipeline.exec();
  } catch (err) {
    logger.debug(`Engagement scoring failed for ${contactId}:`, err);
  }
}

export function registerEngagementScoringListener(): void {
  // --- High-value actions ---

  eventBus.subscribe('payment.donation.completed', 'engagement:donation', async (payload) => {
    const contactId = await findContactByEmail(payload.email);
    if (contactId) await addScore(contactId, 50);
  });

  eventBus.subscribe('payment.subscription.activated', 'engagement:subscription', async (payload) => {
    const contactId = await findContactByEmail(payload.email);
    if (contactId) await addScore(contactId, 40);
  });

  eventBus.subscribe('payment.product.purchased', 'engagement:purchase', async (payload) => {
    const contactId = await findContactByEmail(payload.email);
    if (contactId) await addScore(contactId, 30);
  });

  // --- Volunteer actions ---

  eventBus.subscribe('shift.signup.created', 'engagement:shift-signup', async (payload) => {
    const contactId = await findContactByEmail(payload.userEmail);
    if (contactId) await addScore(contactId, 20);
  });

  eventBus.subscribe('canvass.visit.recorded', 'engagement:canvass-visit', async (payload) => {
    const contactId = await findContactByEmail(payload.email);
    if (contactId) await addScore(contactId, 15);
  });

  eventBus.subscribe('response.submitted', 'engagement:response', async (payload) => {
    const contactId = await findContactByEmail(payload.userEmail);
    if (contactId) await addScore(contactId, 15);
  });

  // --- Communication actions ---

  eventBus.subscribe('campaign.email.sent', 'engagement:email-sent', async (payload) => {
    const contactId = await findContactByEmail(payload.email);
    if (contactId) await addScore(contactId, 10);
  });

  eventBus.subscribe('sms.message.received', 'engagement:sms-received', async (payload) => {
    const contactId = await findContactByPhone(payload.phone);
    if (contactId) await addScore(contactId, 10);
  });

  eventBus.subscribe('listmonk.email.clicked', 'engagement:email-clicked', async (payload) => {
    const contactId = await findContactByEmail(payload.subscriberEmail);
    if (contactId) await addScore(contactId, 8);
  });

  eventBus.subscribe('listmonk.email.opened', 'engagement:email-opened', async (payload) => {
    const contactId = await findContactByEmail(payload.subscriberEmail);
    if (contactId) await addScore(contactId, 5);
  });

  // --- Passive actions ---

  eventBus.subscribe('media.video.viewed', 'engagement:video-view', async (payload) => {
    if (!payload.userId) return;
    try {
      const prisma = await lazyPrisma();
      const user = await prisma.user.findUnique({
        where: { id: payload.userId },
        select: { email: true },
      });
      if (!user) return;
      const contactId = await findContactByEmail(user.email);
      if (contactId) await addScore(contactId, 3);
    } catch {
      // silent
    }
  });
}

/**
 * Utility functions for querying engagement scores.
 * Can be imported by API routes for score display.
 */
export const engagementScoring = {
  /** Get a single contact's score */
  async getScore(contactId: string): Promise<number> {
    try {
      const redis = await lazyRedis();
      const score = await redis.get(`engagement:score:${contactId}`);
      return score ? parseInt(score, 10) : 0;
    } catch {
      return 0;
    }
  },

  /** Get top N contacts by engagement score */
  async getLeaderboard(limit = 20): Promise<Array<{ contactId: string; score: number }>> {
    try {
      const redis = await lazyRedis();
      const results = await redis.zrevrange('engagement:leaderboard', 0, limit - 1, 'WITHSCORES');
      const leaderboard: Array<{ contactId: string; score: number }> = [];
      for (let i = 0; i < results.length; i += 2) {
        leaderboard.push({ contactId: results[i], score: parseInt(results[i + 1], 10) });
      }
      return leaderboard;
    } catch {
      return [];
    }
  },

  /** Get last activity timestamp for a contact */
  async getLastActivity(contactId: string): Promise<string | null> {
    try {
      const redis = await lazyRedis();
      return await redis.get(`engagement:last:${contactId}`);
    } catch {
      return null;
    }
  },
};
