import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { recordAudit } from '../observability/audit';

const DAY_MS = 86_400_000;

export interface SweepResult {
  archived: number;
  redacted: number;
  skippedHeld: number;
}

/**
 * Data retention (P9). Each interaction gets a per-resource retention snapshot with
 * frozen archive/delete timestamps; a scheduled sweep archives (status change) then
 * deletes = redacts-in-place (reusing the DSR tombstone semantics) once a resource ages
 * past its window. An active compliance hold blocks both. Never a hard delete.
 */
@Injectable()
export class RetentionService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(RetentionService.name);
  private timer?: ReturnType<typeof setInterval>;

  constructor(private readonly prisma: PrismaService) {}

  onModuleInit(): void {
    const ms = Number(process.env.IIOS_RETENTION_SWEEP_INTERVAL_MS ?? 0);
    if (ms > 0) {
      this.timer = setInterval(() => {
        void this.sweep().catch((err) => this.logger.warn(`retention sweep failed: ${(err as Error).message}`));
      }, ms);
    }
  }

  onModuleDestroy(): void {
    if (this.timer) clearInterval(this.timer);
  }

  private windowDays(dataClass: string, kind: 'ARCHIVE' | 'DELETE'): number {
    const cls = process.env[`IIOS_RETENTION_${kind}_DAYS_${dataClass.toUpperCase()}`];
    const glob = process.env[`IIOS_RETENTION_${kind}_DAYS`];
    return Number(cls ?? glob ?? (kind === 'ARCHIVE' ? 90 : 365));
  }

  /** Lazily capture a per-resource snapshot for any interaction that lacks one. */
  async ensureSnapshots(scopeId?: string): Promise<number> {
    const interactions = await this.prisma.iiosInteraction.findMany({
      where: scopeId ? { scopeId } : {},
      select: { id: true, scopeId: true, dataClass: true, receivedAt: true },
    });
    let created = 0;
    for (const i of interactions) {
      const exists = await this.prisma.iiosRetentionPolicySnapshot.findUnique({
        where: { targetType_targetId: { targetType: 'interaction', targetId: i.id } },
      });
      if (exists) continue;
      const base = i.receivedAt.getTime();
      await this.prisma.iiosRetentionPolicySnapshot.create({
        data: {
          policyKey: `default:${i.dataClass}:v1`,
          scopeSnapshotId: i.scopeId,
          targetType: 'interaction',
          targetId: i.id,
          dataClass: i.dataClass,
          archiveAfter: new Date(base + this.windowDays(i.dataClass, 'ARCHIVE') * DAY_MS),
          deleteAfter: new Date(base + this.windowDays(i.dataClass, 'DELETE') * DAY_MS),
          sourceVersion: process.env.IIOS_RETENTION_POLICY_VERSION ?? 'v1',
        },
      });
      created++;
    }
    return created;
  }

  /**
   * Capture a retention snapshot for any outbound command that lacks one. An email/SMS command
   * holds PII (the recipient address, and the rendered body with their name), so it gets a single
   * window and goes STRAIGHT to redact (archiveAfter == deleteAfter — no archive phase). Unscoped
   * system sends use an 'unscoped' partition tag so the global sweep still reaches them.
   */
  async ensureOutboundSnapshots(scopeId?: string): Promise<number> {
    const commands = await this.prisma.iiosOutboundCommand.findMany({
      where: scopeId ? { scopeId } : {},
      select: { id: true, scopeId: true, createdAt: true },
    });
    let created = 0;
    for (const c of commands) {
      const exists = await this.prisma.iiosRetentionPolicySnapshot.findUnique({
        where: { targetType_targetId: { targetType: 'outbound_command', targetId: c.id } },
      });
      if (exists) continue;
      const deleteAt = new Date(c.createdAt.getTime() + this.windowDays('outbound', 'DELETE') * DAY_MS);
      await this.prisma.iiosRetentionPolicySnapshot.create({
        data: {
          policyKey: 'outbound:pii:v1',
          scopeSnapshotId: c.scopeId ?? 'unscoped',
          targetType: 'outbound_command',
          targetId: c.id,
          dataClass: 'outbound',
          archiveAfter: deleteAt,
          deleteAfter: deleteAt,
          sourceVersion: process.env.IIOS_RETENTION_POLICY_VERSION ?? 'v1',
        },
      });
      created++;
    }
    return created;
  }

  /** Act on due snapshots: archive, or delete (redact-in-place), honoring compliance holds. */
  async applySweep(scopeId?: string): Promise<SweepResult> {
    const now = new Date();
    const due = await this.prisma.iiosRetentionPolicySnapshot.findMany({
      where: { status: { in: ['ACTIVE', 'ARCHIVED'] }, archiveAfter: { lte: now }, ...(scopeId ? { scopeSnapshotId: scopeId } : {}) },
    });
    const result: SweepResult = { archived: 0, redacted: 0, skippedHeld: 0 };

    for (const s of due) {
      const held = await this.prisma.iiosComplianceHold.findFirst({
        where: { targetId: s.targetId, status: 'ACTIVE', OR: [{ expiresAt: null }, { expiresAt: { gt: now } }] },
      });
      if (held) {
        result.skippedHeld++;
        continue;
      }

      if (s.deleteAfter <= now) {
        if (s.targetType === 'outbound_command') {
          // Redact the recipient address + rendered body; KEEP the template provenance columns.
          await this.prisma.$transaction([
            this.prisma.iiosOutboundCommand.update({ where: { id: s.targetId }, data: { target: '[redacted]', payload: { redacted: true } } }),
            this.prisma.iiosRetentionPolicySnapshot.update({ where: { id: s.id }, data: { status: 'REDACTED' } }),
          ]);
          await recordAudit(this.prisma, { action: 'retention.redacted', resourceType: 'outbound_command', resourceId: s.targetId, scopeId: s.scopeSnapshotId });
        } else {
          await this.prisma.$transaction([
            this.prisma.iiosMessagePart.updateMany({ where: { interactionId: s.targetId }, data: { bodyText: '[redacted]', contentRef: null } }),
            this.prisma.iiosInboundRawEvent.updateMany({ where: { interactionId: s.targetId }, data: { payload: { redacted: true } } }),
            this.prisma.iiosInteraction.update({ where: { id: s.targetId }, data: { status: 'REDACTED' } }),
            this.prisma.iiosRetentionPolicySnapshot.update({ where: { id: s.id }, data: { status: 'REDACTED' } }),
          ]);
          await recordAudit(this.prisma, { action: 'retention.redacted', resourceType: 'interaction', resourceId: s.targetId, scopeId: s.scopeSnapshotId });
        }
        result.redacted++;
      } else if (s.status === 'ACTIVE') {
        await this.prisma.iiosRetentionPolicySnapshot.update({ where: { id: s.id }, data: { status: 'ARCHIVED' } });
        await recordAudit(this.prisma, { action: 'retention.archived', resourceType: 'interaction', resourceId: s.targetId, scopeId: s.scopeSnapshotId });
        result.archived++;
      }
    }
    return result;
  }

  /** Full sweep: capture missing snapshots (interactions + outbound commands), then act on due ones. */
  async sweep(scopeId?: string): Promise<SweepResult> {
    await this.ensureSnapshots(scopeId);
    await this.ensureOutboundSnapshots(scopeId);
    return this.applySweep(scopeId);
  }

  /** Snapshot lifecycle counts for /metrics (global ops). */
  async summary(): Promise<{ total: number; byStatus: Record<string, number> }> {
    const groups = await this.prisma.iiosRetentionPolicySnapshot.groupBy({ by: ['status'], _count: true });
    const byStatus: Record<string, number> = {};
    let total = 0;
    for (const g of groups) {
      byStatus[g.status] = g._count;
      total += g._count;
    }
    return { total, byStatus };
  }
}
