import Redis from 'ioredis';
import { prisma } from '../config/database';
import { env } from '../config/env';
import { logger } from '../utils/logger';
import { siteSettingsService } from '../modules/settings/settings.service';
import { notificationQueueService } from './notification-queue.service';
import { eventBus } from './event-bus.service';

/**
 * Volunteer Re-Engagement Scanner
 *
 * Queries volunteers whose last activity (ShiftSignup or CanvassSession)
 * exceeds the configured inactivity threshold. Sends a re-engagement email
 * with a Redis-based cooldown to prevent spamming.
 */
class ReengagementService {
  private redis: Redis | null = null;

  private getRedis(): Redis {
    if (!this.redis) {
      this.redis = new Redis(env.REDIS_URL);
    }
    return this.redis;
  }

  /**
   * Run the re-engagement scan. Called daily from server.ts.
   */
  async scan(): Promise<{ scanned: number; sent: number; skipped: number }> {
    const settings = await siteSettingsService.get();

    if (!settings.notifyVolunteerReengagement) {
      return { scanned: 0, sent: 0, skipped: 0 };
    }

    const inactiveDays = settings.reengagementInactiveDays || 30;
    const cooldownDays = settings.reengagementCooldownDays || 30;
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - inactiveDays);

    // Find volunteers who have past shift signups or canvass sessions
    // but whose LAST activity is before the cutoff date.
    const volunteers = await this.findInactiveVolunteers(cutoffDate);

    let sent = 0;
    let skipped = 0;
    const signupUrl = `${env.CORS_ORIGINS.split(',')[0].trim()}/shifts`;

    for (const volunteer of volunteers) {
      try {
        // Check Redis cooldown
        const cooldownKey = `reengagement:${volunteer.email}`;
        const redis = this.getRedis();
        const exists = await redis.exists(cooldownKey);

        if (exists) {
          skipped++;
          continue;
        }

        // Enqueue the re-engagement notification
        await notificationQueueService.enqueue({
          type: 'volunteer-reengagement',
          volunteerEmail: volunteer.email,
          volunteerName: volunteer.name || volunteer.email,
          lastActivityDate: volunteer.lastActivityDate.toLocaleDateString('en-CA', {
            year: 'numeric',
            month: 'long',
            day: 'numeric',
          }),
          lastActivityType: volunteer.lastActivityType,
          signupUrl,
        });

        // Set cooldown in Redis (expires after cooldownDays)
        const cooldownSeconds = cooldownDays * 24 * 60 * 60;
        await redis.set(cooldownKey, '', 'EX', cooldownSeconds);

        // Publish re-engagement event
        eventBus.publish('reengagement.sent', {
          email: volunteer.email,
          name: volunteer.name || volunteer.email,
        });

        sent++;
      } catch (err) {
        logger.error(`Re-engagement failed for ${volunteer.email}:`, err);
        skipped++;
      }
    }

    if (sent > 0 || skipped > 0) {
      logger.info(`Re-engagement scan: ${volunteers.length} inactive volunteers found, ${sent} emails queued, ${skipped} skipped (cooldown/error)`);
    }

    return { scanned: volunteers.length, sent, skipped };
  }

  /**
   * Find volunteers whose last activity is before the cutoff date.
   * Activity = ShiftSignup (confirmed) OR CanvassSession (completed).
   */
  private async findInactiveVolunteers(cutoffDate: Date): Promise<Array<{
    email: string;
    name: string | null;
    lastActivityDate: Date;
    lastActivityType: string;
  }>> {
    // Get users who have at least one signup or canvass session
    // and whose MOST RECENT activity is before the cutoff.
    // We use two separate queries and merge results.

    // 1. Users with shift signups (last signup before cutoff)
    const shiftVolunteers = await prisma.shiftSignup.groupBy({
      by: ['userEmail', 'userName'],
      _max: { signupDate: true },
      where: { status: 'CONFIRMED' },
      having: {
        signupDate: { _max: { lt: cutoffDate } },
      },
    });

    // 2. Users with canvass sessions (last session before cutoff)
    const canvassVolunteers = await prisma.canvassSession.groupBy({
      by: ['userId'],
      _max: { startedAt: true },
      where: { status: 'COMPLETED' },
      having: {
        startedAt: { _max: { lt: cutoffDate } },
      },
    });

    // Look up user details for canvass volunteers
    const canvassUserIds = canvassVolunteers.map((c) => c.userId);
    const canvassUsers = canvassUserIds.length > 0
      ? await prisma.user.findMany({
          where: { id: { in: canvassUserIds } },
          select: { id: true, email: true, name: true },
        })
      : [];
    const canvassUserMap = new Map(canvassUsers.map((u) => [u.id, u]));

    // Merge into a single list, taking the most recent activity per email
    const volunteerMap = new Map<string, {
      email: string;
      name: string | null;
      lastActivityDate: Date;
      lastActivityType: string;
    }>();

    for (const sv of shiftVolunteers) {
      const date = sv._max.signupDate;
      if (!date) continue;
      const email = sv.userEmail;
      const existing = volunteerMap.get(email);
      if (!existing || date > existing.lastActivityDate) {
        volunteerMap.set(email, {
          email,
          name: sv.userName,
          lastActivityDate: date,
          lastActivityType: 'Shift Signup',
        });
      }
    }

    for (const cv of canvassVolunteers) {
      const date = cv._max?.startedAt;
      if (!date) continue;
      const user = canvassUserMap.get(cv.userId);
      if (!user) continue;
      const existing = volunteerMap.get(user.email);
      if (!existing || date > existing.lastActivityDate) {
        volunteerMap.set(user.email, {
          email: user.email,
          name: user.name,
          lastActivityDate: date,
          lastActivityType: 'Canvass Session',
        });
      }
    }

    // Filter out entries where the merged last activity is actually after cutoff
    // (e.g., shift signup was old but canvass session was recent)
    return Array.from(volunteerMap.values()).filter(
      (v) => v.lastActivityDate < cutoffDate,
    );
  }

  async close() {
    if (this.redis) {
      this.redis.disconnect();
      this.redis = null;
    }
  }
}

export const reengagementService = new ReengagementService();
