import {
  type AccountingEventSchema,
  type PostingResult,
  type ValidationResult,
  AccountingEventType,
} from './accounting-event-registry';
import type { PostingContext } from './context-types';
import { EventRegistry } from './event-registry';

export interface PostingEngineOptions {
  idempotencyStore?: Map<string, PostingResult>;
}

export class AccountingPostingEngine {
  private readonly registry: EventRegistry;
  private readonly idempotencyStore: Map<string, PostingResult>;

  constructor(registry: EventRegistry, options: PostingEngineOptions = {}) {
    this.registry = registry;
    this.idempotencyStore = options.idempotencyStore || new Map<string, PostingResult>();
  }

  async postAccountingEvent<T>(
    eventType: AccountingEventType,
    payload: T,
    context: PostingContext,
  ): Promise<PostingResult> {
    const schema = this.registry.get<T>(eventType);

    if (schema.version !== context.postingRuleVersion) {
      return {
        success: false,
        error: `postingRuleVersion mismatch: schema=${schema.version}, context=${context.postingRuleVersion}`,
      };
    }

    const idempotencyKey = this.buildIdempotencyKey(eventType, context);
    const cached = this.idempotencyStore.get(idempotencyKey);
    if (cached) return cached;

    const validation: ValidationResult = schema.validate(payload);
    if (!validation.valid) {
      return {
        success: false,
        error: (validation.errors || []).join('; '),
      };
    }

    const result = await this.invokeHandler(schema, payload, {
      ...context,
      event: eventType,
    });

    if (result.success) {
      this.idempotencyStore.set(idempotencyKey, result);
    }

    return result;
  }

  private async invokeHandler<T>(
    schema: AccountingEventSchema<T>,
    payload: T,
    context: PostingContext,
  ): Promise<PostingResult> {
    return schema.handler(payload, context);
  }

  private buildIdempotencyKey(eventType: AccountingEventType, context: PostingContext): string {
    return [
      eventType,
      context.documentId,
      context.postingSequence,
      context.postingRuleVersion,
    ].join(':');
  }
}
