import { Injectable, Inject } from "@nestjs/common";
import { Prisma, AuditAction } from "@prisma/client";
import { AuditLogRepository } from "../database/repositories/index.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { Logger } from "nestjs-pino";
import { extractClientIp, extractUserAgent } from "@bff/core/http/request-context.util.js";

// Re-export AuditAction from Prisma for consumers
export { AuditAction } from "@prisma/client";

export interface AuditLogData {
  userId?: string | undefined;
  action: AuditAction;
  resource?: string | undefined;
  details?: Record<string, unknown> | string | number | boolean | null | undefined;
  ipAddress?: string | undefined;
  userAgent?: string | undefined;
  success?: boolean | undefined;
  error?: string | undefined;
}

/**
 * Minimal request shape for audit logging.
 * Compatible with Express Request but only requires the fields needed for IP/UA extraction.
 * Must be compatible with RequestContextLike from request-context.util.ts.
 */
export type AuditRequest = {
  headers?: Record<string, string | string[] | undefined> | undefined;
  ip?: string | undefined;
  connection?: { remoteAddress?: string | undefined } | undefined;
  socket?: { remoteAddress?: string | undefined } | undefined;
};

/**
 * Service for writing audit log entries.
 *
 * Handles all audit write operations: creating log entries and
 * logging authentication events with request context extraction.
 */
@Injectable()
export class AuditLogService {
  constructor(
    private readonly auditLogRepository: AuditLogRepository,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async log(data: AuditLogData): Promise<void> {
    try {
      const createData: Prisma.AuditLogUncheckedCreateInput = {
        action: data.action,
        success: data.success ?? true,
      };

      if (data.userId !== undefined) createData.userId = data.userId;
      if (data.resource !== undefined) createData.resource = data.resource;
      if (data.ipAddress !== undefined) createData.ipAddress = data.ipAddress;
      if (data.userAgent !== undefined) createData.userAgent = data.userAgent;
      if (data.error !== undefined) createData.error = data.error;
      if (data.details !== undefined) {
        createData.details =
          data.details === null
            ? Prisma.JsonNull
            : (JSON.parse(JSON.stringify(data.details)) as Prisma.InputJsonValue);
      }

      await this.auditLogRepository.create(createData);
    } catch (error) {
      this.logger.error("Audit logging failed", {
        errorType: error instanceof Error ? error.constructor.name : "Unknown",
        message: extractErrorMessage(error),
      });
    }
  }

  async logAuthEvent(
    action: AuditAction,
    userId?: string,
    details?: Record<string, unknown> | string | number | boolean | null,
    request?: AuditRequest,
    success: boolean = true,
    error?: string
  ): Promise<void> {
    const ipAddress = extractClientIp(request);
    const userAgent = extractUserAgent(request);

    await this.log({
      userId,
      action,
      resource: "auth",
      details,
      ipAddress,
      userAgent,
      success,
      error,
    });
  }
}
