/**
 * Automatic run retention — cap total rows and optional max age.
 * Deletes oldest **terminal** runs only (completed / failed / killed); never active runs.
 */

import { createLogger } from '@4runr/shared';
import { getPersistenceMode } from '../config/persistence.js';
import { getPrismaClient } from '../db/prisma.js';
import { getRunStore } from './index.js';
import type { Run, RunStatus } from './types.js';

const logger = createLogger('Gateway:RunRetention');

const TERMINAL_STATUSES: RunStatus[] = ['completed', 'failed', 'killed'];

export interface RunRetentionConfig {
  enabled: boolean;
  /** Keep at most this many runs (0 = disable count-based trim). Default 200. */
  maxCount: number;
  /** Delete terminal runs older than this many days (0 = disabled). Default 0. */
  maxAgeDays: number;
}

export interface RunRetentionResult {
  deletedByCount: number;
  deletedByAge: number;
  totalAfter: number;
}

export function getRunRetentionConfig(): RunRetentionConfig {
  const maxCountRaw =
    process.env['RUN_RETENTION_MAX_COUNT'] ??
    (process.env['NODE_ENV'] === 'production' ? '500' : '100');
  const maxAgeRaw = parseInt(process.env['RUN_RETENTION_MAX_AGE_DAYS'] ?? '0', 10);
  const maxCountParsed = parseInt(maxCountRaw, 10);
  return {
    enabled: process.env['RUN_RETENTION_ENABLED'] !== 'false',
    maxCount:
      Number.isFinite(maxCountParsed) && maxCountParsed >= 0 ? maxCountParsed : 100,
    maxAgeDays: Number.isFinite(maxAgeRaw) && maxAgeRaw >= 0 ? maxAgeRaw : 0,
  };
}

export function isTerminalRunForRetention(status: RunStatus): boolean {
  return TERMINAL_STATUSES.includes(status);
}

/** Non-blocking hook after POST /api/runs */
export function scheduleRunRetentionEnforcement(): void {
  setImmediate(() => {
    enforceRunRetention().catch((err: unknown) => {
      const msg = err instanceof Error ? err.message : String(err);
      logger.warn('Run retention enforcement failed', { error: msg });
    });
  });
}

export async function enforceRunRetention(
  config: RunRetentionConfig = getRunRetentionConfig(),
): Promise<RunRetentionResult> {
  if (!config.enabled) {
    return { deletedByCount: 0, deletedByAge: 0, totalAfter: 0 };
  }

  const mode = getPersistenceMode();
  if (mode === 'memory') {
    return enforceMemoryRetention(config);
  }
  return enforcePostgresRetention(config);
}

async function enforceMemoryRetention(config: RunRetentionConfig): Promise<RunRetentionResult> {
  const store = getRunStore();
  const all = await store.listRuns();
  let deletedByAge = 0;
  let deletedByCount = 0;

  if (config.maxAgeDays > 0) {
    const cutoff = Date.now() - config.maxAgeDays * 24 * 60 * 60 * 1000;
    for (const run of all) {
      if (!isTerminalRunForRetention(run.status)) continue;
      if (new Date(run.createdAt).getTime() < cutoff) {
        await store.deleteRun(run.id);
        deletedByAge++;
      }
    }
  }

  const remaining = await store.listRuns();
  if (config.maxCount > 0 && remaining.length > config.maxCount) {
    const terminal = remaining
      .filter((r) => isTerminalRunForRetention(r.status))
      .sort(
        (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
      );
    const excess = remaining.length - config.maxCount;
    const toDelete = terminal.slice(0, excess);
    for (const run of toDelete) {
      await store.deleteRun(run.id);
      deletedByCount++;
    }
  }

  const totalAfter = (await store.listRuns()).length;
  logIfDeleted(deletedByCount, deletedByAge, totalAfter, config);
  return { deletedByCount, deletedByAge, totalAfter };
}

async function enforcePostgresRetention(config: RunRetentionConfig): Promise<RunRetentionResult> {
  const prisma = await getPrismaClient();
  let deletedByAge = 0;
  let deletedByCount = 0;

  const terminalWhere = {
    status: { in: ['COMPLETED', 'FAILED', 'KILLED'] as const },
  };

  if (config.maxAgeDays > 0) {
    const cutoff = new Date(Date.now() - config.maxAgeDays * 24 * 60 * 60 * 1000);
    const ageResult = await prisma.run.deleteMany({
      where: {
        ...terminalWhere,
        createdAt: { lt: cutoff },
      },
    });
    deletedByAge = ageResult.count;
  }

  if (config.maxCount > 0) {
    const total: number = await prisma.run.count();
    if (total > config.maxCount) {
      const excess = total - config.maxCount;
      const oldest = await prisma.run.findMany({
        where: terminalWhere,
        orderBy: { createdAt: 'asc' },
        take: excess,
        select: { id: true },
      });
      if (oldest.length > 0) {
        const countResult = await prisma.run.deleteMany({
          where: { id: { in: oldest.map((r: { id: string }) => r.id) } },
        });
        deletedByCount = countResult.count;
      }
    }
  }

  const totalAfter: number = await prisma.run.count();
  logIfDeleted(deletedByCount, deletedByAge, totalAfter, config);
  return { deletedByCount, deletedByAge, totalAfter };
}

function logIfDeleted(
  deletedByCount: number,
  deletedByAge: number,
  totalAfter: number,
  config: RunRetentionConfig,
): void {
  if (deletedByCount > 0 || deletedByAge > 0) {
    logger.info('Run retention applied', {
      deletedByCount,
      deletedByAge,
      totalAfter,
      maxCount: config.maxCount,
      maxAgeDays: config.maxAgeDays,
    });
  }
}

/** @internal test helper */
export function sortRunsOldestFirst(runs: Run[]): Run[] {
  return [...runs].sort(
    (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
  );
}
