import type { LogRecord, LogTransport } from "./types.js";

export interface JsonLinesHttpTransportOptions {
  endpoint: string;
  batchSize?: number;
  flushIntervalMs?: number;
  maxBufferSize?: number;
  redactFields?: string[];
  fetchImpl?: typeof fetch;
}

export class JsonLinesHttpTransport implements LogTransport {
  private readonly endpoint: string;
  private readonly batchSize: number;
  private readonly maxBufferSize: number;
  private readonly redactFields: Set<string>;
  private readonly fetchFn: typeof fetch;
  private buffer: LogRecord[] = [];
  private timer: ReturnType<typeof setInterval> | null = null;

  constructor(options: JsonLinesHttpTransportOptions) {
    this.endpoint = options.endpoint;
    this.batchSize = options.batchSize ?? 50;
    this.maxBufferSize = options.maxBufferSize ?? 500;
    this.redactFields = new Set(
      (options.redactFields ?? ["password", "token", "secret", "authorization"])
        .map((f) => f.toLowerCase()),
    );
    this.fetchFn = options.fetchImpl ?? globalThis.fetch;

    const intervalMs = options.flushIntervalMs ?? 5000;
    this.timer = setInterval(() => {
      if (this.buffer.length > 0) {
        void this.sendBatch();
      }
    }, intervalMs);

    // Unref so the timer doesn't keep the Node process alive
    if (typeof this.timer === "object" && "unref" in this.timer) {
      this.timer.unref();
    }
  }

  write(record: LogRecord): void {
    const redacted = this.redact(record);
    this.buffer.push(redacted);

    // Backpressure: drop oldest if buffer exceeds max
    while (this.buffer.length > this.maxBufferSize) {
      this.buffer.shift();
    }

    // Flush if batch is full
    if (this.buffer.length >= this.batchSize) {
      void this.sendBatch();
    }
  }

  async flush(): Promise<void> {
    if (this.buffer.length === 0) return;
    await this.sendBatch();
  }

  private async sendBatch(): Promise<void> {
    const batch = this.buffer.splice(0, this.buffer.length);
    if (batch.length === 0) return;

    const body = batch.map((r) => JSON.stringify(r)).join("\n");

    try {
      await this.fetchFn(this.endpoint, {
        method: "POST",
        headers: { "Content-Type": "application/x-ndjson" },
        body,
      });
    } catch {
      // Silently drop failed sends — logging a log failure causes recursion.
      // In production, a metrics counter would track send failures.
    }
  }

  private redact(record: LogRecord): LogRecord {
    if (this.redactFields.size === 0) return record;

    const fields = { ...record.fields };
    for (const key of Object.keys(fields)) {
      if (this.redactFields.has(key.toLowerCase())) {
        fields[key] = "[REDACTED]";
      }
    }
    return { ...record, fields };
  }
}
