/**
 * Memory Compressor
 *
 * Compresses old detailed feedback into durable 1-3 sentence learnings.
 *
 * Compression schedule:
 * - 0-7 days:  Full raw events, referenced directly
 * - 8-30 days: Raw events + aggregates, summarized weekly
 * - 31-90 days: Raw archived, patterns only
 * - 90+ days:  Only durable rules and anti-patterns preserved
 *
 * Rules with <5 occurrences that are >90 days old get pruned (noise).
 */

import * as fs from "node:fs";
import * as path from "node:path";
import type { EnrichedFeedbackEvent, ThemeCluster } from "../types.js";
import { loadFeedbackEvents } from "../storage/feedback-store.js";

// ═══════════════════════════════════════════════════════════════
// COMPRESSION
// ═══════════════════════════════════════════════════════════════

export interface CompressedLearning {
  /** Durable 1-3 sentence summary */
  summary: string;
  /** Source: how many raw events this was derived from */
  sourceEventCount: number;
  /** When the pattern was first observed */
  firstSeen: string;
  /** When the pattern was last reinforced */
  lastSeen: string;
  /** Confidence level 0-1 */
  confidence: number;
  /** Is this learning still actively reinforced? */
  isActive: boolean;
}

/**
 * Compress feedback events older than `ageDays` into durable learnings.
 */
export async function compressFeedback(
  dataDir: string,
  ageDays: number = 30
): Promise<CompressedLearning[]> {
  // Load all events
  const allEvents = await loadFeedbackEvents(dataDir, 365);

  const cutoff = new Date(Date.now() - ageDays * 24 * 60 * 60 * 1000);
  const oldEvents = allEvents.filter(
    (e) => new Date(e.timestamp) < cutoff
  );
  const recentEvents = allEvents.filter(
    (e) => new Date(e.timestamp) >= cutoff
  );

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

  // Group old events by theme
  const themeGroups = groupByTheme(oldEvents);
  const learnings: CompressedLearning[] = [];

  for (const [theme, events] of themeGroups) {
    // Check if this theme is still active in recent events
    const isActive = recentEvents.some(
      (e) => e._themes?.includes(theme)
    );

    // Prune noise: <5 occurrences and >90 days old = noise
    const oldestEvent = events.reduce((oldest, e) =>
      new Date(e.timestamp) < new Date(oldest.timestamp) ? e : oldest
    );
    const ageMs = Date.now() - new Date(oldestEvent.timestamp).getTime();
    const ageDaysOldest = ageMs / (24 * 60 * 60 * 1000);

    if (events.length < 5 && ageDaysOldest > 90 && !isActive) {
      continue; // Prune this theme
    }

    // Generate compressed learning
    const summary = generateSummary(theme, events);
    const timestamps = events.map((e) => e.timestamp).sort();

    learnings.push({
      summary,
      sourceEventCount: events.length,
      firstSeen: timestamps[0]!,
      lastSeen: timestamps[timestamps.length - 1]!,
      confidence: Math.min(1.0, events.length / 15), // Scales up with more evidence
      isActive,
    });
  }

  return learnings.sort((a, b) => b.confidence - a.confidence);
}

/**
 * Generate a compressed summary for a group of feedback events.
 */
function generateSummary(
  theme: string,
  events: EnrichedFeedbackEvent[]
): string {
  const totalEvents = events.length;
  const decisions = events.filter((e) => e.feedback.decision);
  const rejections = decisions.filter(
    (e) => e.feedback.decision?.decision === "rejected"
  );
  const approvals = decisions.filter(
    (e) => e.feedback.decision?.decision === "approved"
  );

  // Collect unique feedback snippets
  const snippets = new Set<string>();
  for (const event of events) {
    if (event.feedback.freeText?.disliked) {
      snippets.add(event.feedback.freeText.disliked.slice(0, 100));
    }
    if (event.feedback.decision?.reason) {
      snippets.add(event.feedback.decision.reason.slice(0, 100));
    }
  }

  // Build summary
  const parts: string[] = [];

  if (rejections.length > approvals.length) {
    parts.push(
      `"${theme}" has been flagged in ${totalEvents} reviews with a ${((rejections.length / decisions.length) * 100).toFixed(0)}% rejection correlation.`
    );
  } else if (approvals.length > rejections.length) {
    parts.push(
      `"${theme}" appears in ${totalEvents} reviews and correlates with ${((approvals.length / decisions.length) * 100).toFixed(0)}% approval rate.`
    );
  } else {
    parts.push(
      `"${theme}" appeared in ${totalEvents} reviews with mixed outcomes.`
    );
  }

  // Add the most representative feedback snippet
  const topSnippet = [...snippets][0];
  if (topSnippet) {
    parts.push(`Example: "${topSnippet}"`);
  }

  return parts.join(" ");
}

/**
 * Group events by their themes.
 */
function groupByTheme(
  events: EnrichedFeedbackEvent[]
): Map<string, EnrichedFeedbackEvent[]> {
  const groups = new Map<string, EnrichedFeedbackEvent[]>();

  for (const event of events) {
    for (const theme of event._themes ?? []) {
      if (!groups.has(theme)) {
        groups.set(theme, []);
      }
      groups.get(theme)!.push(event);
    }
  }

  return groups;
}

/**
 * Archive old JSONL files to a cold storage directory.
 */
export async function archiveOldFiles(
  dataDir: string,
  olderThanDays: number = 90
): Promise<{ archived: string[]; totalSizeBytes: number }> {
  const rawDir = path.join(dataDir, "feedback", "raw");
  const archiveDir = path.join(dataDir, "feedback", "archive");

  if (!fs.existsSync(rawDir)) return { archived: [], totalSizeBytes: 0 };

  const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000);
  const files = fs.readdirSync(rawDir).filter((f) => f.endsWith(".jsonl"));
  const archived: string[] = [];
  let totalSize = 0;

  for (const file of files) {
    const dateStr = file.replace(".jsonl", "");
    const fileDate = new Date(dateStr + "T00:00:00Z");

    if (fileDate < cutoff) {
      if (!fs.existsSync(archiveDir)) {
        fs.mkdirSync(archiveDir, { recursive: true });
      }

      const srcPath = path.join(rawDir, file);
      const dstPath = path.join(archiveDir, file);
      const stats = fs.statSync(srcPath);

      fs.renameSync(srcPath, dstPath);
      archived.push(file);
      totalSize += stats.size;
    }
  }

  return { archived, totalSizeBytes: totalSize };
}
