import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, EntityManager } from 'typeorm';
import { OutboxEvent } from './outbox.entity';

export interface PublishEventParams {
  aggregateType: string;
  aggregateId: string;
  eventType: string;
  topic: string;
  payload: Record<string, any>;
  partitionKey?: string;
  headers?: Record<string, any>;
}

/**
 * Outbox Service - writes domain events to the outbox table.
 * MUST be called within the same database transaction as the business operation.
 *
 * Usage:
 *   await manager.transaction(async (txManager) => {
 *     await txManager.save(entity);
 *     await outboxService.publishWithinTransaction(txManager, { ... });
 *   });
 */
@Injectable()
export class OutboxService {
  constructor(
    @InjectRepository(OutboxEvent)
    private readonly outboxRepo: Repository<OutboxEvent>,
  ) {}

  /**
   * Write an event to the outbox table within an existing transaction.
   * This is the PRIMARY method - ensures atomicity with business data.
   */
  async publishWithinTransaction(
    manager: EntityManager,
    params: PublishEventParams,
  ): Promise<OutboxEvent> {
    const event = manager.create(OutboxEvent, {
      aggregateType: params.aggregateType,
      aggregateId: params.aggregateId,
      eventType: params.eventType,
      topic: params.topic,
      partitionKey: params.partitionKey || params.aggregateId,
      payload: params.payload,
      headers: {
        ...params.headers,
        source: params.aggregateType,
        timestamp: new Date().toISOString(),
      },
      status: 'pending',
      retryCount: 0,
      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24h expiry
    });
    return manager.save(OutboxEvent, event);
  }

  /**
   * Convenience method when no explicit transaction is needed.
   * Creates its own transaction wrapping only the outbox insert.
   */
  async publish(params: PublishEventParams): Promise<OutboxEvent> {
    const event = this.outboxRepo.create({
      aggregateType: params.aggregateType,
      aggregateId: params.aggregateId,
      eventType: params.eventType,
      topic: params.topic,
      partitionKey: params.partitionKey || params.aggregateId,
      payload: params.payload,
      headers: {
        ...params.headers,
        source: params.aggregateType,
        timestamp: new Date().toISOString(),
      },
      status: 'pending',
      retryCount: 0,
      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
    });
    return this.outboxRepo.save(event);
  }
}
