/**
 * Phase 7 — Integration Bus (Event Pub/Sub)
 * -----------------------------------------
 * ناقل أحداث بسيط بين الوحدات/العقد:
 *  - on(topic, handler)
 *  - once(topic, handler)
 *  - off(topic, handler)
 *  - emit(topic, data)
 *  - request(topic, req) <-> reply(topic: `${topic}:reply`)
 *
 * يدعم wildcards بالشكل البسيط: "cfp:*" يطابق "cfp:..." .
 * يدعم تسجيل middleware (قبل التسليم) + لوج اختياري JSONL.
 */

import fs from 'node:fs';
import path from 'node:path';

type Handler<T = unknown> = (data: T, meta: { topic: string; ts: string }) => void | Promise<void>;

interface Subscription {
  topic: string;
  handler: Handler<any>;
  once?: boolean;
}

type Middleware = (evt: {
  topic: string;
  data: any;
  ts: string;
}) => { topic: string; data: any; ts: string } | void;

export class IntegrationBus {
  private subs: Subscription[] = [];
  private mws: Middleware[] = [];
  private logging = false;
  private logPath = './storage/integration_bus.log.jsonl';

  enableLogging(pathOverride?: string) {
    this.logging = true;
    if (pathOverride) this.logPath = pathOverride;
    fs.mkdirSync(path.dirname(this.logPath), { recursive: true });
  }

  disableLogging() {
    this.logging = false;
  }

  use(mw: Middleware) {
    this.mws.push(mw);
  }

  on<T = unknown>(topic: string, handler: Handler<T>) {
    this.subs.push({ topic, handler });
    return () => this.off(topic, handler);
  }

  once<T = unknown>(topic: string, handler: Handler<T>) {
    this.subs.push({ topic, handler, once: true });
    return () => this.off(topic, handler);
  }

  off<T = unknown>(topic: string, handler: Handler<T>) {
    this.subs = this.subs.filter((s) => !(s.topic === topic && s.handler === handler));
  }

  /**
   * emit: يرسل حدثاً لكل المشتركين المتوافقين مع الموضوع (مع دعم wildcard).
   */
  async emit<T = unknown>(topic: string, data: T) {
    const ts = new Date().toISOString();
    let evt = { topic, data, ts };

    // middlewares
    for (const mw of this.mws) {
      const out = mw(evt);
      if (out) evt = out;
    }

    // logging
    if (this.logging) {
      fs.appendFileSync(this.logPath, JSON.stringify(evt) + '\n');
    }

    const matches = this.subs.filter((s) => this.topicMatch(s.topic, evt.topic));
    for (const s of matches) {
      await s.handler(evt.data, { topic: evt.topic, ts: evt.ts });
    }
    // clean once
    this.subs = this.subs.filter((s) => !(s.once && matches.includes(s)));
  }

  /**
   * request-response نمط بسيط: يرجع Promise ينتظر أول رد على `${topic}:reply`
   */
  request<TReq = unknown, TRes = unknown>(
    topic: string,
    req: TReq,
    timeoutMs = 3000
  ): Promise<TRes> {
    return new Promise(async (resolve, reject) => {
      const replyTopic = `${topic}:reply`;
      const off = this.once<TRes>(replyTopic, (res) => {
        off();
        resolve(res);
      });

      // أرسل الطلب
      await this.emit(topic, req);

      const t = setTimeout(() => {
        off();
        reject(new Error(`IntegrationBus.request timeout on ${topic}`));
      }, timeoutMs);

      // safety: clear if resolved earlier
      (resolve as any).finally?.(() => clearTimeout(t));
    });
  }

  /**
   * مساعدة: تطابق Wildcard بسيط (suffix = "*")
   */
  private topicMatch(subTopic: string, emittedTopic: string) {
    if (subTopic === emittedTopic) return true;
    if (subTopic.endsWith('*')) {
      const prefix = subTopic.slice(0, -1);
      return emittedTopic.startsWith(prefix);
    }
    return false;
  }
}

// Singleton افتراضي للاستخدام السريع
export const Bus = new IntegrationBus();
