import {EventData} from '@config/socket/EventsProcessor';
import {Storage} from '@services/mmkvFactory';

type EventWithAttempts = EventData & {
  attempts?: number;
};

const MAX_ATTEMPTS = 3;

/**
 * Persists socket events that failed during processing so they can be retried
 * on the next sync run instead of being lost after an app restart.
 *
 * Events are keyed by their `ts` value and keep a retry counter. Once an event
 * reaches the maximum number of failed attempts, it is removed from this queue
 * and the optional removal callback is executed so the caller can update any
 * related processing state.
 */
class UnprocessedEventsService {
  private store = new Storage({id: 'unprocessed_events_store'});

  private didRemove: Array<(ts: string) => void> = [];

  addDidRemoveCallback(callback: (ts: string) => void) {
    this.didRemove.push(callback);
    return () => {
      this.didRemove = this.didRemove.filter(cb => cb !== callback);
    };
  }

  // Remove the event once it has been processed successfully.
  onSuccess(ts: string | undefined) {
    this.remove(ts);
  }

  // Persists event on first failure. Pass allowIncrement=true only from
  // processUnprocessedEvents to advance retry counter. Other failure sites use
  // default false to avoid burning through the retry budget.
  onError(event: EventData, allowIncrement = false) {
    const ts = event.data.ts as string | undefined;
    if (!ts) return;

    if (this.has(ts)) {
      if (allowIncrement) {
        this.increaseAttempts(ts);
      }
    } else {
      this.add(event);
    }
  }

  getAll(): Record<string, EventWithAttempts> {
    const events: Record<string, EventWithAttempts> = {};
    this.store.getAllKeys().forEach(key => {
      const eventStr = this.store.getString(key);
      if (eventStr) {
        try {
          events[key] = JSON.parse(eventStr) as EventWithAttempts;
        } catch (error) {
          this.store.remove(key);
        }
      } else {
        this.store.remove(key);
      }
    });
    return events;
  }

  cleanup() {
    this.store.clearAll();
  }

  private add(event: EventData) {
    const ts = event.data.ts as string | undefined;
    if (ts) {
      // attempts starts at 1: first failure counted as first attempt, subsequent retries tracked via increaseAttempts()
      const stored: EventWithAttempts = {...event, attempts: 1};
      this.store.set(ts, JSON.stringify(stored));
    }
  }

  private increaseAttempts(ts: string | undefined) {
    if (!ts) return;

    const eventStr = this.store.getString(ts);
    if (eventStr) {
      try {
        const event = JSON.parse(eventStr) as EventWithAttempts;
        event.attempts = event.attempts ?? 1;
        if (event.attempts >= MAX_ATTEMPTS) {
          this.store.remove(ts);
          this.didRemove.forEach(callback => callback(ts));
        } else {
          event.attempts += 1;
          this.store.set(ts, JSON.stringify(event));
        }
      } catch (error) {
        this.store.remove(ts);
      }
    }
  }

  private has(ts: string | undefined): boolean {
    // Guard against events without a timestamp.
    if (!ts) return false;
    return this.store.contains(ts);
  }

  private remove(ts: string | undefined) {
    // Guard against events without a timestamp.
    if (ts) {
      this.store.remove(ts);
    }
  }
}

export const unprocessedEventsService = new UnprocessedEventsService();
