import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { mergeMap, Observable } from 'rxjs';
import { AuthContextService, type AuthenticatedRequest } from '../auth/auth-context.service';
import { AuditService } from '../../modules/audit/audit.service';
import { AUDIT_MUTATION_METADATA_KEY, type AuditMutationMetadata } from './audit-mutation.decorator';

type MutationRequest = AuthenticatedRequest & {
  method?: string;
  originalUrl?: string;
  url?: string;
  params?: Record<string, string>;
  body?: Record<string, unknown>;
};

@Injectable()
export class AuditMutationInterceptor implements NestInterceptor {
  constructor(
    private readonly reflector: Reflector,
    private readonly audit: AuditService,
    private readonly authContext: AuthContextService,
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const metadata = this.reflector.getAllAndOverride<AuditMutationMetadata>(AUDIT_MUTATION_METADATA_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!metadata) return next.handle();

    const request = context.switchToHttp().getRequest<MutationRequest>();
    return next.handle().pipe(mergeMap(async (result) => {
      const actor = await this.authContext.resolveCurrentUser(request);
      const entity = extractEntity(result);
      const scope = resolveScope(metadata, request, entity);
      await this.audit.record({
        actor,
        action: metadata.action,
        entityType: metadata.entityType,
        entityId: resolveEntityId(metadata, request, entity),
        productId: scope.productId,
        projectId: scope.projectId,
        versionId: scope.versionId,
        scope,
        after: result,
        metadata: { route: `${request.method ?? 'UNKNOWN'} ${request.originalUrl ?? request.url ?? ''}`.trim() },
      });
      return result;
    }));
  }
}

function extractEntity(result: unknown): Record<string, unknown> | undefined {
  if (!result || typeof result !== 'object') return undefined;
  const record = result as Record<string, unknown>;
  if (record.item && typeof record.item === 'object') return record.item as Record<string, unknown>;
  if (Array.isArray(record.items) && record.items[0] && typeof record.items[0] === 'object') return record.items[0] as Record<string, unknown>;
  return record;
}

function resolveEntityId(metadata: AuditMutationMetadata, request: MutationRequest, entity: Record<string, unknown> | undefined) {
  const fromParam = metadata.entityIdParam ? request.params?.[metadata.entityIdParam] : undefined;
  const fromEntity = entity?.id;
  return fromParam ?? (typeof fromEntity === 'string' ? fromEntity : 'unknown');
}

function resolveScope(metadata: AuditMutationMetadata, request: MutationRequest, entity: Record<string, unknown> | undefined) {
  return compact({
    productId: scopedValue(metadata.productIdParam, metadata.productIdBody, 'productId', request, entity),
    projectId: scopedValue(metadata.projectIdParam, metadata.projectIdBody, 'projectId', request, entity),
    versionId: scopedValue(metadata.versionIdParam, metadata.versionIdBody, 'versionId', request, entity),
  });
}

function scopedValue(
  paramKey: string | undefined,
  bodyKey: string | undefined,
  resultKey: string,
  request: MutationRequest,
  entity: Record<string, unknown> | undefined,
): string | undefined {
  const value = (paramKey ? request.params?.[paramKey] : undefined)
    ?? (bodyKey ? request.body?.[bodyKey] : undefined)
    ?? entity?.[resultKey];
  return typeof value === 'string' && value ? value : undefined;
}

function compact<T extends Record<string, string | undefined>>(value: T): { [K in keyof T]?: string } {
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item)) as { [K in keyof T]?: string };
}
