import { Injectable } from '@nestjs/common';
import { PrismaService } from '../database/prisma.service';
import { Prisma } from '@prisma/client';

export const OUTBOX_STATUSES = ['pending', 'processing', 'published', 'failed'] as const;
export type OutboxStatus = (typeof OUTBOX_STATUSES)[number];

export interface CreateOutboxInput {
  eventType: string;
  aggregateType: string;
  aggregateId: string;
  dedupeKey: string;
  payload: Record<string, unknown>;
  availableAt?: Date;
}

/**
 * M-AI-02-07: Transactional Outbox Repository
 *
 * 约束：
 * - createInTransaction 支持在外部 Prisma Transaction 中原子写入
 * - 不自行开启独立事务
 * - 不向 BullMQ 发布
 * - dedupeKey UNIQUE 保证幂等
 */
@Injectable()
export class OutboxRepository {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * 在给定的事务客户端中创建 Outbox 事件。
   * 若未传 tx，使用 this.prisma（非事务模式，仅测试用）。
   *
   * 调用方负责在同一个 tx 中同时写入业务数据，
   * 以确保 Job + Outbox 的原子性。
   */
  async createInTransaction(
    tx: Prisma.TransactionClient,
    input: CreateOutboxInput,
  ) {
    return tx.outboxEvent.create({
      data: {
        eventType: input.eventType,
        aggregateType: input.aggregateType,
        aggregateId: input.aggregateId,
        dedupeKey: input.dedupeKey,
        payload: input.payload as any,
        availableAt: input.availableAt ?? new Date(),
      },
    });
  }

  /**
   * 查找可分发的事件：status=pending 且 availableAt 已到。
   * 按 availableAt ASC 保证 FIFO。
   */
  async findDispatchable(limit: number = 50) {
    return this.prisma.outboxEvent.findMany({
      where: {
        status: 'pending',
        availableAt: { lte: new Date() },
      },
      orderBy: { availableAt: 'asc' },
      take: limit,
    });
  }

  /**
   * 标记为 processing（CAS：仅当 status 仍为 'pending' 时成功）。
   * 返回更新后的记录；若被其他 Worker 抢占则返回 null。
   */
  async markProcessing(eventId: string, lockedBy: string) {
    const result = await this.prisma.outboxEvent.updateMany({
      where: {
        id: eventId,
        status: 'pending',
      },
      data: {
        status: 'processing',
        lockedAt: new Date(),
        lockedBy,
      },
    });

    if (result.count === 0) return null;

    return this.prisma.outboxEvent.findUnique({
      where: { id: eventId },
    });
  }

  /** 标记为 published */
  async markPublished(eventId: string) {
    return this.prisma.outboxEvent.update({
      where: { id: eventId },
      data: {
        status: 'published',
        publishedAt: new Date(),
        lockedAt: null,
        lockedBy: null,
      },
    });
  }

  /** 标记为 failed，递增 attemptCount */
  async markFailed(eventId: string, errorCode?: string, errorMessage?: string) {
    return this.prisma.outboxEvent.update({
      where: { id: eventId },
      data: {
        status: 'failed',
        lastErrorCode: errorCode,
        lastErrorMessage: errorMessage,
        attemptCount: { increment: 1 },
        lockedAt: null,
        lockedBy: null,
      },
    });
  }

  /**
   * 释放过期锁：将 lockedAt 超过 thresholdMs 的 processing 事件重置为 pending。
   * 返回释放数量。
   */
  async releaseExpiredLocks(thresholdMs: number = 60_000) {
    const cutoff = new Date(Date.now() - thresholdMs);
    const result = await this.prisma.outboxEvent.updateMany({
      where: {
        status: 'processing',
        lockedAt: { lt: cutoff },
      },
      data: {
        status: 'pending',
        lockedAt: null,
        lockedBy: null,
      },
    });
    return result.count;
  }
}
