import { ReplaySubject } from 'rxjs';
import { SocketService } from './services/socket.service';
import { Serializable, TypedJSON } from 'typedjson';
import { CloudEvent } from 'cloudevents';

export class EventStreamListener<T> {
  public Feed: ReplaySubject<CloudEvent<T>[]> = new ReplaySubject<CloudEvent<T>[]>();
  private socketService: SocketService;
  private serializer: TypedJSON<T>;
  private sourcePrefix: string;
  private subjectPrefix: string;
  private handleFeedEventsCallback = (events: any[]) => this.handleFeedEvents(events);

  constructor(public eventType: string, sourcePrefix: string, subjectPrefix: string, socketService: SocketService, serializer: TypedJSON<T>) {
    this.eventType = eventType;
    this.serializer = serializer;
    this.socketService = socketService;
    this.sourcePrefix = sourcePrefix;
    this.subjectPrefix = subjectPrefix;
    this.setupFeed();
  }

  private setupFeed() {
    this.socketService.getSocket().on('event', this.handleFeedEventsCallback);
    this.socketService.registerFilter({ type: this.eventType, sourcePrefix : this.sourcePrefix, subjectPrefix : this.subjectPrefix });
  }

  private handleFeedEvents(events: any[]) {
    const eventBatch = events.map((event) => {
      if (event.type === this.eventType && this.isValidEvent(event)) {
        event.data = this.serializer.parse(event.data);
        return event;
      }
      return null;
    }).filter(Boolean);

    if (eventBatch.length === 0) {
      return;
    }
    // Push events into the Feed observable
    this.Feed.next(eventBatch as CloudEvent<T>[]);
  }

  public async requestHistory(fromTime?: string, toTime?: string, callback?: (eventBatch: CloudEvent<T>[]) => void): Promise<EventStreamListener<T>> {
    return new Promise((resolve, reject) => {
      const request = {
        eventType: this.eventType,
        subjectPrefix: this.subjectPrefix,
        sourcePrefix: this.sourcePrefix,
        fromTime,
        toTime,
      };

      const onHistory = (events: any[]) => {
        const eventBatch = events.map((event) => {
          if (event.type === this.eventType && this.isValidEvent(event)) {
            event.data = this.serializer.parse(event.data);
            return event;
          }
          return null;
        }).filter(Boolean);

        if (callback) {
          callback(eventBatch as CloudEvent<T>[]);
        }
      };

      const onHistoryComplete = (event: any) => {
        if (event.eventType === this.eventType) {
          this.socketService.getSocket().off('history', onHistory);
          this.socketService.getSocket().off('historyComplete', onHistoryComplete);
          resolve(this);
        }
      };

      // Listen for history data and history completion
      this.socketService.getSocket().on('history', onHistory);
      this.socketService.getSocket().on('historyComplete', onHistoryComplete);

      // Emit the request for history
      this.socketService.requestHistory(this.eventType, this.subjectPrefix, this.sourcePrefix, fromTime, toTime);
    });
  }

  public async requestLatest(callback?: (eventBatch: CloudEvent<T>[]) => void): Promise<EventStreamListener<T>> {
    return new Promise((resolve, reject) => {
      const request = {
        eventType: this.eventType,
        subjectPrefix: this.subjectPrefix,
        sourcePrefix: this.sourcePrefix
      };

      const onHistory = (events: any[]) => {
        const eventBatch = events.map((event) => {
          if (event.type === this.eventType && this.isValidEvent(event)) {
            event.data = this.serializer.parse(event.data);
            return event;
          }
          return null;
        }).filter(Boolean);

        if (callback) {
          callback(eventBatch as CloudEvent<T>[]);
        }
      };

      const onHistoryComplete = (event: any) => {
        if (event.eventType === this.eventType) {
          this.socketService.getSocket().off('history', onHistory);
          this.socketService.getSocket().off('historyComplete', onHistoryComplete);
          resolve(this);
        }
      };

      // Listen for history data and history completion
      this.socketService.getSocket().on('history', onHistory);
      this.socketService.getSocket().on('historyComplete', onHistoryComplete);

      // Emit the request for history
      this.socketService.requestLatest(this.eventType, this.subjectPrefix, this.sourcePrefix);
    });
  }

  private isValidEvent(event: any): boolean {
    return (!this.sourcePrefix || event.source.startsWith(this.sourcePrefix)) &&
           (!this.subjectPrefix || event.subject.startsWith(this.subjectPrefix));
  }

  public close(): void {
    this.socketService.getSocket().off('event', this.handleFeedEventsCallback);
  }
}
