/**
 * EventIngestor — Single runtime entry point for events from agents/tools/workers
 *
 * Implements: ingest(durable) → EventStore.append, ingest_ephemeral → EventBus.publish
 * Per system-detailed-design.md §5.1 and runtime-semantics-v1.md §2.
 *
 * Rules:
 * - Never creates scheduler tasks, permission decisions, or memory promotions itself
 * - Those are follow-up events emitted by owning services
 *
 * @module packages/runtime/src/events/EventIngestor
 */

import type { RuntimeEvent, EventFilter } from '@aircoding/contracts'
import { eventSchemaRegistry, type EventPersistence } from './EventSchemaRegistry.js'
import { eventBus, type EventBus } from './EventBus.js'

// Import EventStore lazily to avoid circular dependency
let _eventStore: any = null
async function getEventStore() {
  if (!_eventStore) {
    // Use dynamic import for ESM
    const mod = await import('./EventStore.js')
    _eventStore = mod.eventStore
  }
  return _eventStore
}

// =============================================================================
// Interfaces (for backward compatibility with existing code)
// =============================================================================

/**
 * EventIngestor interface - the single runtime entry point for events
 * from agents/tools/workers per runtime-semantics §2 and DD §5.1.
 */
export interface IEventIngestor {
  /**
   * Ingest a durable event - validated, stored in EventStore, projected to domain tables.
   * Throws AirError{kind:"system_error"} on validation failure.
   */
  ingest<T>(event: RuntimeEvent<T>): Promise<void>

  /**
   * Ingest an ephemeral event - validated, published to EventBus only.
   * Does not persist to EventStore or project to domain tables.
   */
  ingest_ephemeral<T>(event: RuntimeEvent<T>): Promise<void>
}

/**
 * EventIngestorFactory creates an EventIngestor for a given session.
 * The actual implementation wires up EventStore, EventBus, and domain projection.
 */
export interface EventIngestorFactory {
  /**
   * Create an EventIngestor for the given session.
   * The ingestor is bound to a specific session's EventStore.
   */
  createForSession(sessionId: string): IEventIngestor
}

/**
 * NullEventIngestor - a no-op implementation for testing or when events aren't needed.
 */
export class NullEventIngestor implements IEventIngestor {
  async ingest<T>(_event: RuntimeEvent<T>): Promise<void> {
    // No-op
  }

  async ingest_ephemeral<T>(_event: RuntimeEvent<T>): Promise<void> {
    // No-op
  }
}

/**
 * Creates a NullEventIngestor instance.
 */
export function createNullEventIngestor(): IEventIngestor {
  return new NullEventIngestor()
}

// =============================================================================
// EventIngestor Implementation
// =============================================================================

/**
 * EventIngestor is the single runtime entry point for all events.
 *
 * Flow (runtime-semantics §2):
 * 1. Validate envelope + schema/version (EventSchemaRegistry)
 * 2. Look up persistence policy by event.type
 * 3. If durable: EventStore.append(event) → tx + projection + post-commit publish
 * 4. If ephemeral: EventBus.publish(event) → live only
 *
 * The ingestor never creates scheduler tasks, permission decisions, or memory promotions.
 * Those are follow-up events emitted by owning services.
 */
export class EventIngestorImpl implements IEventIngestor {
  private bus: EventBus

  constructor(options?: {
    bus?: EventBus
  }) {
    this.bus = options?.bus ?? eventBus
  }

  /**
   * Ingest a durable event.
   * Validates, looks up persistence policy, delegates to EventStore.append.
   */
  async ingest<T>(event: RuntimeEvent<T>): Promise<void> {
    // Validate event envelope
    this.validateEnvelope(event)

    // Get persistence policy
    const persistence = this.policyFor(event.type, event.version)
    if (persistence !== 'durable') {
      throw new Error(
        `Event ${event.type} is ${persistence}, use ingest_ephemeral() for ephemeral events`,
      )
    }

    // Delegate to EventStore (which handles tx + projection + post-commit publish)
    const store = await getEventStore()
    await store.append(event)
  }

  /**
   * Ingest an ephemeral event.
   * Validates, looks up persistence policy, delegates to EventBus.publish.
   */
  async ingest_ephemeral<T>(event: RuntimeEvent<T>): Promise<void> {
    // Validate event envelope
    this.validateEnvelope(event)

    // Get persistence policy
    const persistence = this.policyFor(event.type, event.version)
    if (persistence !== 'ephemeral') {
      throw new Error(
        `Event ${event.type} is ${persistence}, use ingest() for durable events`,
      )
    }

    // Publish directly to EventBus (live transport only)
    this.bus.publish(event)
  }

  /**
   * Ingest multiple events in batch.
   */
  async ingest_batch<T>(events: RuntimeEvent<T>[], policy: 'durable' | 'ephemeral'): Promise<void> {
    if (events.length === 0) return

    for (const event of events) {
      this.validateEnvelope(event)
      const eventPersistence = this.policyFor(event.type, event.version)
      if (eventPersistence !== policy) {
        throw new Error(
          `Event ${event.type} has persistence ${eventPersistence}, expected ${policy}`,
        )
      }
    }

    if (policy === 'durable') {
      const store = await getEventStore()
      await store.append_many(events as RuntimeEvent<unknown>[])
    } else {
      for (const event of events) {
        this.bus.publish(event)
      }
    }
  }

  /**
   * Query durable events from storage.
   */
  async query(filter: EventFilter): Promise<RuntimeEvent[]> {
    const store = await getEventStore()
    return store.query(filter)
  }

  /**
   * Get the persistence policy for an event type.
   */
  policyFor(type: string, version: number): EventPersistence {
    const persistence = eventSchemaRegistry.getPersistence(type, version)
    if (persistence === undefined) {
      throw new Error(`Unknown event type: ${type}@v${version}`)
    }
    return persistence
  }

  /**
   * Validate the basic event envelope structure.
   */
  private validateEnvelope<T>(event: RuntimeEvent<T>): void {
    if (!event.id) throw new Error('Event missing required field: id')
    if (!event.type) throw new Error('Event missing required field: type')
    if (event.version === undefined || event.version === null) {
      throw new Error('Event missing required field: version')
    }
    if (!event.timestamp) throw new Error('Event missing required field: timestamp')
    if (!event.session_id) throw new Error('Event missing required field: session_id')
    if (!event.source) throw new Error('Event missing required field: source')
    if (!Array.isArray(event.route)) throw new Error('Event field route must be an array')
    if (event.payload === undefined) throw new Error('Event missing required field: payload')
    if (!eventSchemaRegistry.isRegistered(event.type, event.version)) {
      throw new Error(`Unregistered event type: ${event.type}@v${event.version}`)
    }
  }
}

// Default singleton - also export as EventIngestor for compatibility
export const eventIngestor = new EventIngestorImpl()

// Alias for backward compatibility
export const EventIngestor = EventIngestorImpl

// Export type for consumers
export type { EventPersistence } from './EventSchemaRegistry.js'