import { createId } from "@paralleldrive/cuid2";
import { Inject, Injectable } from "@nestjs/common";
import type { NewOutboxRow, OutboxTable } from "./schema.js";
import {
  OUTBOX_CONFIG,
  OUTBOX_DB,
  OUTBOX_LOGGER,
  OUTBOX_TABLE,
  type OutboxConfig,
  type OutboxDbClient,
  type OutboxLogger,
  type PublishOptions,
} from "./types.js";

/**
 * OutboxService —— 事务性 Outbox 写入服务。
 *
 * 调用方在业务事务内调用 `publish`，将事件记录写入 outbox 表，
 * 与业务写在同一事务中原子提交，保证“业务变更”与“事件发布”的一致性。
 *
 * 由独立的 OutboxPublisher 负责异步轮询 pending 记录并投递到 Kafka，
 * 实现 at-least-once 投递语义。
 */
@Injectable()
export class OutboxService {
  constructor(
    @Inject(OUTBOX_DB) private readonly db: OutboxDbClient,
    @Inject(OUTBOX_TABLE) private readonly table: OutboxTable,
    @Inject(OUTBOX_CONFIG) private readonly config: OutboxConfig,
    @Inject(OUTBOX_LOGGER) private readonly logger: OutboxLogger,
  ) {}

  /**
   * 写入一条 outbox 记录。
   *
   * @param eventType 事件类型（如 `ExamCreated`），用于 Kafka topic 路由
   * @param payload   事件负载，将被 JSON 序列化存入 outbox 行
   * @param options   可选：aggregateId / metadata / delayMs / tx
   * @returns event id（cuid2），供调用方追踪；同时作为 Kafka 消息 key 实现幂等去重
   *
   * 事务管理：传入 `options.tx` 则在调用方事务内写入（推荐，保证与业务表原子提交）；
   * 省略时使用模块注入的 db 执行单条插入（仅保证 outbox 行自身的原子性）。
   */
  async publish(
    eventType: string,
    payload: unknown,
    options?: PublishOptions,
  ): Promise<string> {
    const id = createId();
    const now = new Date();
    const nextRetryAt =
      options?.delayMs !== undefined && options.delayMs > 0
        ? new Date(now.getTime() + options.delayMs)
        : null;

    const record: NewOutboxRow = {
      id,
      eventType,
      payload: JSON.stringify(payload),
      aggregateId: options?.aggregateId ?? id,
      maxRetryCount: this.config.maxRetryCount,
      nextRetryAt,
      metadata: options?.metadata ?? null,
    };

    await (options?.tx ?? this.db).insert(this.table).values(record);

    this.logger.debug(
      { id, eventType, aggregateId: record.aggregateId },
      "Outbox record enqueued",
    );
    return id;
  }
}
