import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import type { CurrentUser } from '../../common/auth/auth-context.service';
import type { QueryAuditEventsDto } from './dto/query-audit-events.dto';

export interface AuditRecordInput {
  actor?: CurrentUser | null;
  action: string;
  entityType: string;
  entityId: string;
  productId?: string | null;
  projectId?: string | null;
  versionId?: string | null;
  scope?: unknown;
  before?: unknown;
  after?: unknown;
  metadata?: unknown;
}

@Injectable()
export class AuditService {
  constructor(private readonly prisma: PrismaService) {}

  record(input: AuditRecordInput) {
    return this.prisma.auditEvent.create({
      data: {
        actorId: input.actor?.id ?? null,
        actorName: input.actor?.name ?? input.actor?.username ?? '',
        action: input.action,
        entityType: input.entityType,
        entityId: input.entityId,
        productId: input.productId ?? null,
        projectId: input.projectId ?? null,
        versionId: input.versionId ?? null,
        scope: toJson(input.scope ?? {}),
        before: input.before === undefined ? undefined : toJson(redactSensitive(input.before)),
        after: input.after === undefined ? undefined : toJson(redactSensitive(input.after)),
        metadata: toJson(redactSensitive(input.metadata ?? {})),
      },
    });
  }

  query(query: QueryAuditEventsDto) {
    return this.prisma.auditEvent.findMany({
      where: buildWhere(query),
      orderBy: { createdAt: 'desc' },
      take: clampTake(query.take),
    });
  }
}

function buildWhere(query: QueryAuditEventsDto) {
  const where: Record<string, unknown> = {};
  for (const key of ['actorId', 'entityType', 'entityId', 'productId', 'projectId', 'versionId'] as const) {
    if (query[key]) where[key] = query[key];
  }

  const dateRange: Record<string, Date> = {};
  if (query.dateFrom) dateRange.gte = new Date(query.dateFrom);
  if (query.dateTo) dateRange.lte = new Date(query.dateTo);
  if (Object.keys(dateRange).length > 0) where.createdAt = dateRange;
  return where;
}

function clampTake(value: string | undefined): number {
  const parsed = Number(value ?? 50);
  if (!Number.isFinite(parsed) || parsed <= 0) return 50;
  return Math.min(100, Math.floor(parsed));
}

function redactSensitive(value: unknown): unknown {
  if (Array.isArray(value)) return value.map((item) => redactSensitive(item));
  if (value instanceof Date) return value.toISOString();
  if (!value || typeof value !== 'object') return value;

  return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([key, item]) => [
    key,
    isSensitiveKey(key) ? '[REDACTED]' : redactSensitive(item),
  ]));
}

function isSensitiveKey(key: string): boolean {
  const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, '');
  return ['password', 'token', 'secret', 'apikey', 'authorization'].some((sensitive) => normalized.includes(sensitive));
}

function toJson(value: unknown): Prisma.InputJsonValue {
  return value as Prisma.InputJsonValue;
}
