/**
 * Violation Service
 * Manages user violation records and mute penalties.
 *
 * Penalty tiers:
 * - 3 violations → 24-hour mute
 * - 5 violations → 7-day mute
 * - 10 violations → permanent mute
 *
 * When a mute period expires, the count for the current penalty tier is reset.
 */

const AuditLogger = require('./auditLogger');

// ============================================================
// Penalty Tiers
// ============================================================
const PENALTY_TIERS = [
  { threshold: 3, durationMs: 24 * 60 * 60 * 1000, label: '24小时禁言' },
  { threshold: 5, durationMs: 7 * 24 * 60 * 60 * 1000, label: '7天禁言' },
  { threshold: 10, durationMs: Infinity, label: '永久禁言' },
];

// Scene labels for logging
const SCENE_LABELS = {
  1: 'nickname',
  2: 'chat',
  3: 'signature',
  4: 'description',
  5: 'image',
};

class ViolationService {
  /**
   * @param {object} options
   * @param {import('./auditLogger')} [options.logger] - Audit logger instance
   */
  constructor(options = {}) {
    this.logger = options.logger || new AuditLogger();

    /**
     * In-memory violation records.
     * Key: "game_id:userId" (composite key for tenant isolation)
     * Value: { count, violations[], currentTierStart, gameId, userId }
     * In production, this should be backed by a database.
     * @type {Map<string, { count: number, violations: Array, currentTierStart: number, gameId: string, userId: string }>}
     */
    this._records = new Map();

    /**
     * In-memory mute records.
     * Key: "game_id:userId" (composite key for tenant isolation)
     * Value: { expiresAt: number|null, tier: number }
     * @type {Map<string, { expiresAt: number|null, tier: number }>}
     */
    this._mutes = new Map();

    // Start periodic cleanup of expired mutes
    this._cleanupInterval = setInterval(() => this._cleanupExpiredMutes(), 60000);
  }

  /**
   * Build composite key for tenant isolation.
   * @param {string} gameId
   * @param {string} userId
   * @returns {string}
   */
  _compositeKey(gameId, userId) {
    return `${gameId}:${userId}`;
  }

  /**
   * Record a violation for a user.
   * @param {object} entry
   * @param {string} entry.userId - User's openid
   * @param {string} [entry.gameId] - Game identifier for tenant isolation (default: 'default')
   * @param {string} entry.violationType - Type of violation (e.g., 'text_violation', 'image_violation')
   * @param {string} entry.contentSummary - Desensitized content summary
   * @param {number} entry.scene - Scene value
   * @returns {{ penalty: string|null, isMuted: boolean, muteDuration: string|null }}
   */
  recordViolation(entry) {
    const { userId, violationType, contentSummary, scene } = entry;
    const gameId = entry.gameId || 'default';
    const key = this._compositeKey(gameId, userId);

    // Get or create record
    let record = this._records.get(key);
    if (!record) {
      record = { count: 0, violations: [], currentTierStart: 0, gameId, userId };
      this._records.set(key, record);
    }

    // Increment violation count
    record.count++;
    record.violations.push({
      type: violationType,
      contentSummary,
      scene,
      timestamp: Date.now(),
    });

    // Log the violation
    this.logger.logViolation({
      userId,
      gameId,
      violationType,
      contentSummary,
      action: `violation_count_${record.count}`,
    });

    // Check if a penalty should be applied
    const penalty = this._checkPenalty(key, record.count);

    return penalty;
  }

  /**
   * Check if a user is currently muted.
   * @param {string} userId - User's openid
   * @param {string} [gameId] - Game identifier for tenant isolation (default: 'default')
   * @returns {{ isMuted: boolean, remainingMs: number, remainingText: string, tier: number }}
   */
  getMuteStatus(userId, gameId) {
    const gid = gameId || 'default';
    const key = this._compositeKey(gid, userId);
    const mute = this._mutes.get(key);

    if (!mute) {
      return { isMuted: false, remainingMs: 0, remainingText: '', tier: 0 };
    }

    // Permanent mute
    if (mute.expiresAt === null) {
      return { isMuted: true, remainingMs: Infinity, remainingText: '永久', tier: mute.tier };
    }

    const remaining = mute.expiresAt - Date.now();
    if (remaining <= 0) {
      // Mute expired, clean up
      this._removeMute(key);
      return { isMuted: false, remainingMs: 0, remainingText: '', tier: 0 };
    }

    return {
      isMuted: true,
      remainingMs: remaining,
      remainingText: this._formatDuration(remaining),
      tier: mute.tier,
    };
  }

  /**
   * Remove a mute (used when mute expires or is manually lifted).
   * Resets the violation count for the current penalty tier.
   * @param {string} key - Composite key "gameId:userId"
   */
  _removeMute(key) {
    const record = this._records.get(key);
    if (record) {
      // Find which tier they were at and reset count to just below that tier
      const mute = this._mutes.get(key);
      if (mute && mute.tier > 0) {
        const tierIndex = PENALTY_TIERS.findIndex(t => t.threshold === mute.tier);
        if (tierIndex > 0) {
          record.count = PENALTY_TIERS[tierIndex - 1].threshold;
        } else {
          record.count = 0;
        }
        record.currentTierStart = record.count;
      }
    }

    this._mutes.delete(key);
    console.log(`[ViolationService] Mute removed for key ${key}`);
  }

  /**
   * Check if a penalty should be applied based on violation count.
   * @param {string} key - Composite key "gameId:userId"
   * @param {number} count
   * @returns {{ penalty: string|null, isMuted: boolean, muteDuration: string|null }}
   */
  _checkPenalty(key, count) {
    // Check penalty tiers in reverse order (highest first)
    for (let i = PENALTY_TIERS.length - 1; i >= 0; i--) {
      const tier = PENALTY_TIERS[i];
      if (count >= tier.threshold) {
        // Only apply if not already muted at this or higher tier
        const currentMute = this._mutes.get(key);
        if (currentMute && currentMute.tier >= tier.threshold) {
          return { penalty: null, isMuted: true, muteDuration: null };
        }

        // Apply mute
        const expiresAt = tier.durationMs === Infinity ? null : Date.now() + tier.durationMs;
        this._mutes.set(key, {
          expiresAt,
          tier: tier.threshold,
        });

        console.log(`[ViolationService] User ${key} muted: ${tier.label} (violations: ${count})`);

        // Extract userId and gameId from key for logging
        const colonIdx = key.indexOf(':');
        const gameId = key.substring(0, colonIdx);
        const userId = key.substring(colonIdx + 1);

        this.logger.logViolation({
          userId,
          gameId,
          violationType: 'penalty_applied',
          contentSummary: '',
          action: `mute_${tier.label}`,
        });

        return {
          penalty: tier.label,
          isMuted: true,
          muteDuration: tier.durationMs === Infinity ? '永久' : this._formatDuration(tier.durationMs),
        };
      }
    }

    return { penalty: null, isMuted: false, muteDuration: null };
  }

  /**
   * Clean up expired mutes periodically.
   */
  _cleanupExpiredMutes() {
    const now = Date.now();
    for (const [userId, mute] of this._mutes) {
      if (mute.expiresAt !== null && mute.expiresAt <= now) {
        this._removeMute(userId);
      }
    }
  }

  /**
   * Format a duration in milliseconds to a human-readable string.
   * @param {number} ms
   * @returns {string}
   */
  _formatDuration(ms) {
    if (ms === Infinity) return '永久';

    const totalMinutes = Math.floor(ms / (60 * 1000));
    const hours = Math.floor(totalMinutes / 60);
    const minutes = totalMinutes % 60;
    const days = Math.floor(hours / 24);
    const remainingHours = hours % 24;

    if (days > 0) {
      return `${days}天${remainingHours}小时`;
    }
    return `${hours}小时${minutes}分钟`;
  }

  /**
   * Get violation summary for a user.
   * @param {string} userId
   * @param {string} [gameId] - Game identifier for tenant isolation (default: 'default')
   * @returns {{ count: number, isMuted: boolean, recentViolations: Array, gameId: string }}
   */
  getViolationSummary(userId, gameId) {
    const gid = gameId || 'default';
    const key = this._compositeKey(gid, userId);
    const record = this._records.get(key);
    const muteStatus = this.getMuteStatus(userId, gid);

    return {
      count: record ? record.count : 0,
      isMuted: muteStatus.isMuted,
      muteRemainingText: muteStatus.remainingText,
      recentViolations: record ? record.violations.slice(-5) : [],
      gameId: gid,
    };
  }

  /**
   * Clean up resources.
   */
  destroy() {
    if (this._cleanupInterval) {
      clearInterval(this._cleanupInterval);
    }
    console.log('[ViolationService] Destroyed');
  }
}

module.exports = ViolationService;