import {
  CallHandler,
  ExecutionContext,
  Injectable,
  Logger,
  NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
import { PrismaService } from '../../prisma/prisma.service';
import { AuthenticatedUser } from '../decorators/current-user.decorator';

const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
const SKIP_PATHS = [
  /\/auth\/login$/,
  /\/auth\/refresh$/,
  /\/payments\/webhooks\//,
  /\/signature\/callback$/,
  /\/access\/verify$/,
  /\/visitor-passes\/scan$/,
];

/**
 * Append-only audit trail for all state-changing requests.
 * Writes best-effort (never blocks response). Captures user, IP, action,
 * entity derived from URL, and a sanitized metadata payload.
 */
@Injectable()
export class AuditInterceptor implements NestInterceptor {
  private readonly logger = new Logger(AuditInterceptor.name);

  constructor(private readonly prisma: PrismaService) {}

  intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
    const req = ctx.switchToHttp().getRequest();
    const method = req.method as string;
    const path = req.originalUrl as string;

    if (!WRITE_METHODS.has(method)) return next.handle();
    if (SKIP_PATHS.some((r) => r.test(path))) return next.handle();

    const user: AuthenticatedUser | undefined = req.user;
    const ip: string = req.ip || req.headers['x-forwarded-for'] || '';

    return next.handle().pipe(
      tap({
        next: (body) => this.write(user, method, path, ip, body, 'ok'),
        error: (err) => this.write(user, method, path, ip, { error: err?.message }, 'error'),
      }),
    );
  }

  private async write(
    user: AuthenticatedUser | undefined,
    method: string,
    path: string,
    ip: string,
    body: unknown,
    outcome: 'ok' | 'error',
  ) {
    try {
      const entity = path.split('/').filter(Boolean).slice(2, 4).join('/') || 'unknown';
      const entityId = typeof body === 'object' && body !== null && 'id' in (body as any)
        ? String((body as any).id).slice(0, 64)
        : undefined;
      await this.prisma.auditTrail.create({
        data: {
          tenantId: user?.tenantId ?? null,
          userId: user?.userId ?? null,
          action: `${method} ${outcome}`,
          entity,
          entityId,
          ip: ip?.toString().slice(0, 64),
          metadata: { path } as any,
        },
      });
    } catch (e) {
      this.logger.warn(`audit write failed: ${(e as Error).message}`);
    }
  }
}
