import "reflect-metadata";
import type { Container } from "inversify";
import { getPayload as _getPayload, type SanitizedConfig } from "payload";
import { NoopAuditLog } from "../noop-audit-log";
import { PayloadAuditLog } from "../payload-audit-log";
import { StdoutJsonAuditLog } from "../stdout-json-audit-log";
import { MultiSinkAuditLog } from "../multi-sink-audit-log";
import type { IAuditLog } from "../audit-log.interface";
import { AUDIT_SYMBOLS } from "./symbols";
import { TraceIdEnrichingAuditLog } from "../trace-id-enriching-audit-log";

export type BindAuditOpts = {
  /** Payload config; required if "payload" is in sinks. */
  payloadConfig?: SanitizedConfig;
  /** Sink selection. Default ["payload", "stdout"]. */
  sinks?: ("payload" | "stdout")[];
};

/**
 * Binds an `IAuditLog` impl to the container under `AUDIT_SYMBOLS.IAuditLog`.
 *
 * Default sink set: ["payload", "stdout"] — Payload local cache + structured
 * JSON to stdout (operator wires a log shipper to the centralized aggregator).
 *
 * In production, AUDIT_PSEUDONYM_SALT env var MUST be set. Boot fails fast
 * if not — better to refuse to start than to ship audit data with a dev-fallback
 * salt that an attacker could reverse.
 *
 * The returned auditLog is wrapped in TraceIdEnrichingAuditLog
 * so all sinks receive AuditEntry.correlationId auto-populated from the
 * active OTel span. The inner sink/fan-out is accessible via `.inner`.
 */
export function bindAudit(
  container: Container,
  opts: BindAuditOpts = {},
): { auditLog: IAuditLog } {
  if (
    process.env.NODE_ENV === "production" &&
    !process.env.AUDIT_PSEUDONYM_SALT
  ) {
    throw new Error(
      "AUDIT_PSEUDONYM_SALT environment variable is required in production. " +
        "Generate via `openssl rand -hex 32` and store in your secrets manager.",
    );
  }

  const sinkList = opts.sinks ?? ["payload", "stdout"];
  const sinks: IAuditLog[] = [];
  if (sinkList.includes("payload") && opts.payloadConfig) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    sinks.push(new PayloadAuditLog(opts.payloadConfig, _getPayload as any));
  }
  if (sinkList.includes("stdout")) {
    sinks.push(new StdoutJsonAuditLog());
  }

  const inner: IAuditLog =
    sinks.length > 1
      ? new MultiSinkAuditLog(sinks)
      : sinks.length === 1
        ? sinks[0]!
        : new NoopAuditLog();
  const auditLog: IAuditLog = new TraceIdEnrichingAuditLog(inner);

  if (container.isBound(AUDIT_SYMBOLS.IAuditLog)) {
    container.unbind(AUDIT_SYMBOLS.IAuditLog);
  }
  container.bind<IAuditLog>(AUDIT_SYMBOLS.IAuditLog).toConstantValue(auditLog);

  return { auditLog };
}
