import { v4 as uuidv4 } from 'uuid';
import { createModuleLogger } from '../../utils/logger.js';
import type { QueueItem, QueueItemType, QueueItemStatus } from '../../types/queue.js';

const log = createModuleLogger('queue-system');

// Priority mapping: lower number = higher priority
const TYPE_PRIORITY: Record<QueueItemType, number> = {
  stars_payment: 1,
  chat_response: 2,
  image_generation: 3,
  quality_check: 4,
  channel_publish: 5,
  translation: 5,
};

export type QueueProcessor = (item: QueueItem) => Promise<void>;

export class PriorityQueue {
  private items: QueueItem[] = [];
  private processing = false;
  private processor: QueueProcessor | null = null;
  private onPersist: ((items: QueueItem[]) => Promise<void>) | null = null;

  constructor() {}

  setProcessor(processor: QueueProcessor): void {
    this.processor = processor;
  }

  setOnPersist(callback: (items: QueueItem[]) => Promise<void>): void {
    this.onPersist = callback;
  }

  async add(
    type: QueueItemType,
    characterId: string,
    payload: Record<string, unknown>,
    maxRetries = 3,
  ): Promise<QueueItem> {
    const item: QueueItem = {
      id: uuidv4(),
      type,
      priority: TYPE_PRIORITY[type] || 5,
      status: 'pending',
      characterId,
      payload,
      retryCount: 0,
      maxRetries,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };

    this.items.push(item);
    this.sortQueue();
    await this.persist();
    log.info({ itemId: item.id, type, priority: item.priority }, 'Task added to queue');

    // Auto-start processing if not already running
    if (!this.processing && this.processor) {
      this.processNext();
    }

    return item;
  }

  dequeue(): QueueItem | undefined {
    const index = this.items.findIndex(i => i.status === 'pending');
    if (index === -1) return undefined;

    const item = this.items[index];
    item.status = 'processing';
    item.updatedAt = new Date().toISOString();
    return item;
  }

  async updateStatus(id: string, status: QueueItemStatus, error?: string): Promise<QueueItem | null> {
    const item = this.items.find(i => i.id === id);
    if (!item) return null;

    item.status = status;
    item.updatedAt = new Date().toISOString();
    if (error) item.error = error;
    if (status === 'failed') item.retryCount++;

    await this.persist();
    return item;
  }

  getById(id: string): QueueItem | undefined {
    return this.items.find(i => i.id === id);
  }

  getPending(): QueueItem[] {
    return this.items.filter(i => i.status === 'pending');
  }

  getFailed(): QueueItem[] {
    return this.items.filter(i => i.status === 'failed');
  }

  getAll(): QueueItem[] {
    return [...this.items];
  }

  getStats(): { pending: number; processing: number; completed: number; failed: number } {
    return {
      pending: this.items.filter(i => i.status === 'pending').length,
      processing: this.items.filter(i => i.status === 'processing').length,
      completed: this.items.filter(i => i.status === 'completed').length,
      failed: this.items.filter(i => i.status === 'failed').length,
    };
  }

  async retry(id: string): Promise<boolean> {
    const item = this.items.find(i => i.id === id && i.status === 'failed');
    if (!item) return false;
    if (item.retryCount >= item.maxRetries) return false;

    item.status = 'pending';
    item.error = undefined;
    item.updatedAt = new Date().toISOString();
    this.sortQueue();
    await this.persist();

    if (!this.processing && this.processor) {
      this.processNext();
    }
    return true;
  }

  loadItems(items: QueueItem[]): void {
    this.items = items;
    this.sortQueue();
  }

  private sortQueue(): void {
    this.items.sort((a, b) => {
      // Pending items come first
      if (a.status === 'pending' && b.status !== 'pending') return -1;
      if (a.status !== 'pending' && b.status === 'pending') return 1;
      // Then sort by priority (lower number = higher priority)
      if (a.priority !== b.priority) return a.priority - b.priority;
      // FIFO within same priority
      return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
    });
  }

  private async processNext(): Promise<void> {
    if (this.processing || !this.processor) return;

    const item = this.dequeue();
    if (!item) {
      this.processing = false;
      return;
    }

    this.processing = true;
    try {
      await this.processor(item);
      await this.updateStatus(item.id, 'completed');
    } catch (err: any) {
      log.error({ itemId: item.id, err: err.message }, 'Queue task failed');
      await this.updateStatus(item.id, 'failed', err.message);

      if (item.retryCount < item.maxRetries) {
        await this.retry(item.id);
      }
    }

    this.processing = false;
    // Process next pending item
    const nextPending = this.items.find(i => i.status === 'pending');
    if (nextPending) {
      setImmediate(() => this.processNext());
    }
  }

  private async persist(): Promise<void> {
    if (this.onPersist) {
      const pendingAndFailed = this.items.filter(
        i => i.status === 'pending' || i.status === 'failed' || i.status === 'processing',
      );
      await this.onPersist(pendingAndFailed);
    }
  }
}

// Singleton instance
export const queue = new PriorityQueue();
