/**
 * Dead-Letter Queue (DLQ) Management Service
 *
 * Provides comprehensive DLQ management including:
 * - Listing pending DLQ items with pagination
 * - Marking items as reviewed/resolved
 * - Getting DLQ statistics (age, count by error type)
 * - Replaying failed items
 * - Auto-archival of old items
 * - SLA monitoring and alerts
 *
 * @module lib/services/dlq-management.service
 */

import { prisma } from '@/lib/db';
import { logger } from '@/lib/logger';
import {
  replayDlqItem,
  markDlqItemReviewed,
  getDlqStatsWithSla,
  archiveOldDlqItems,
  getUnreviewedItemsExceedingSla,
  DLQ_RETENTION_CONFIG,
  runDlqArchivalJob,
} from '@/lib/sync-queue';
import { syncQueueDlqAdapter, syncQueueDlqArchiveAdapter } from '@/lib/adapters/sync';

/**
 * DLQ Item with full details
 */
export interface DlqItem {
  id: string;
  originalId: string;
  table: string;
  operation: string;
  data: unknown;
  conflictColumn: string;
  attempts: number;
  lastError: string | null;
  originalCreated: Date;
  movedToDlqAt: Date;
  replayedAt: Date | null;
  replayStatus: string | null;
  reviewedAt: Date | null;
  reviewedBy: string | null;
  resolution: string | null;
}

/**
 * Pagination options for listing DLQ items
 */
export interface DlqListOptions {
  page?: number;
  limit?: number;
  table?: string;
  status?: 'pending' | 'reviewed' | 'replayed' | 'all';
  sortBy?: 'movedToDlqAt' | 'originalCreated' | 'table';
  sortOrder?: 'asc' | 'desc';
}

/**
 * Paginated response for DLQ items
 */
export interface DlqListResponse {
  items: DlqItem[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
    hasNext: boolean;
    hasPrev: boolean;
  };
}

/**
 * DLQ Statistics response
 */
export interface DlqStatistics {
  total: number;
  pending: number;
  reviewed: number;
  replayed: number;
  succeeded: number;
  exceedingSla: number;
  byTable: Record<string, number>;
  byErrorType: Record<string, number>;
  retentionConfig: {
    archiveAfterDays: number;
    reviewSlaHours: number;
  };
  oldestUnreviewed?: {
    id: string;
    table: string;
    hoursOld: number;
  };
  slaStatus: 'ok' | 'warning' | 'critical';
}

/**
 * Review action payload
 */
export interface ReviewAction {
  dlqId: string;
  reviewedBy: string;
  resolution: string;
}

/**
 * Replay action result
 */
export interface ReplayResult {
  dlqId: string;
  success: boolean;
  error?: string;
}

/**
 * Archive action result
 */
export interface ArchiveResult {
  archived: number;
  errors: Array<{ id: string; error: string }>;
}

/**
 * DLQ Management Service
 */
export const dlqManagementService = {
  /**
   * Get paginated list of DLQ items
   */
  async listItems(options: DlqListOptions = {}): Promise<DlqListResponse> {
    const {
      page = 1,
      limit = 20,
      table,
      status = 'all',
      sortBy = 'movedToDlqAt',
      sortOrder = 'desc',
    } = options;

    const skip = (page - 1) * limit;

    // Build where clause
    const where: Record<string, unknown> = {};

    if (table) {
      where.table = table;
    }

    switch (status) {
      case 'pending':
        where.reviewedAt = null;
        where.replayedAt = null;
        break;
      case 'reviewed':
        where.reviewedAt = { not: null };
        break;
      case 'replayed':
        where.replayedAt = { not: null };
        break;
      // 'all' - no additional filters
    }

    try {
      const [items, total] = await Promise.all([
        syncQueueDlqAdapter.findMany({
          where,
          skip,
          take: limit,
          orderBy: { [sortBy]: sortOrder },
        }),
        syncQueueDlqAdapter.count({ where }),
      ]);

      const totalPages = Math.ceil(total / limit);

      return {
        items: items as DlqItem[],
        pagination: {
          page,
          limit,
          total,
          totalPages,
          hasNext: page < totalPages,
          hasPrev: page > 1,
        },
      };
    } catch (error) {
      logger.error('Error listing DLQ items', error);
      throw new Error('Failed to list DLQ items');
    }
  },

  /**
   * Get a single DLQ item by ID
   */
  async getItem(id: string): Promise<DlqItem | null> {
    try {
      const item = await syncQueueDlqAdapter.findFirst({
        where: { id },
      });

      return item as DlqItem | null;
    } catch (error) {
      logger.error('Error getting DLQ item', { id, error });
      throw new Error('Failed to get DLQ item');
    }
  },

  /**
   * Get DLQ statistics with SLA status
   */
  async getStatistics(): Promise<DlqStatistics> {
    const stats = await getDlqStatsWithSla();

    // Determine SLA status
    let slaStatus: 'ok' | 'warning' | 'critical' = 'ok';
    if (stats.exceedingSla > 10) {
      slaStatus = 'critical';
    } else if (stats.exceedingSla > 0) {
      slaStatus = 'warning';
    }

    return {
      ...stats,
      retentionConfig: {
        archiveAfterDays: DLQ_RETENTION_CONFIG.archiveAfterDays,
        reviewSlaHours: DLQ_RETENTION_CONFIG.reviewSlaHours,
      },
      slaStatus,
    };
  },

  /**
   * Mark a DLQ item as reviewed
   */
  async markAsReviewed(action: ReviewAction): Promise<{ success: boolean; error?: string }> {
    return markDlqItemReviewed(action.dlqId, action.reviewedBy, action.resolution);
  },

  /**
   * Mark multiple DLQ items as reviewed
   */
  async markMultipleAsReviewed(
    dlqIds: string[],
    reviewedBy: string,
    resolution: string
  ): Promise<{ succeeded: number; failed: number; errors: Array<{ id: string; error: string }> }> {
    const results = {
      succeeded: 0,
      failed: 0,
      errors: [] as Array<{ id: string; error: string }>,
    };

    for (const dlqId of dlqIds) {
      const result = await markDlqItemReviewed(dlqId, reviewedBy, resolution);
      if (result.success) {
        results.succeeded++;
      } else {
        results.failed++;
        results.errors.push({ id: dlqId, error: result.error || 'Unknown error' });
      }
    }

    logger.info('Bulk DLQ review completed', {
      reviewedBy,
      succeeded: results.succeeded,
      failed: results.failed,
    });

    return results;
  },

  /**
   * Replay a single DLQ item
   */
  async replayItem(dlqId: string): Promise<ReplayResult> {
    const result = await replayDlqItem(dlqId);
    return {
      dlqId,
      success: result.success,
      error: result.error,
    };
  },

  /**
   * Replay multiple DLQ items
   */
  async replayMultipleItems(
    dlqIds: string[]
  ): Promise<{ succeeded: number; failed: number; results: ReplayResult[] }> {
    const results: ReplayResult[] = [];
    let succeeded = 0;
    let failed = 0;

    for (const dlqId of dlqIds) {
      const result = await replayDlqItem(dlqId);
      results.push({ dlqId, success: result.success, error: result.error });

      if (result.success) {
        succeeded++;
      } else {
        failed++;
      }
    }

    logger.info('Bulk DLQ replay completed', { succeeded, failed });

    return { succeeded, failed, results };
  },

  /**
   * Archive old DLQ items (30+ days)
   * This is called by the scheduled job but can also be triggered manually
   */
  async archiveOldItems(): Promise<ArchiveResult> {
    return archiveOldDlqItems();
  },

  /**
   * Get items exceeding review SLA
   */
  async getItemsExceedingSla() {
    return getUnreviewedItemsExceedingSla();
  },

  /**
   * Run the full archival job (archive + SLA check)
   */
  async runArchivalJob() {
    return runDlqArchivalJob();
  },

  /**
   * Get archived DLQ items for audit trail
   */
  async listArchivedItems(options: {
    page?: number;
    limit?: number;
    table?: string;
  } = {}): Promise<{
    items: Array<{
      id: string;
      dlqId: string;
      originalId: string;
      table: string;
      operation: string;
      attempts: number;
      lastError: string | null;
      originalCreated: Date;
      movedToDlqAt: Date;
      archivedAt: Date;
      resolution: string | null;
    }>;
    pagination: {
      page: number;
      limit: number;
      total: number;
      totalPages: number;
    };
  }> {
    const { page = 1, limit = 20, table } = options;
    const skip = (page - 1) * limit;

    const where: Record<string, unknown> = {};
    if (table) {
      where.table = table;
    }

    try {
      const [items, total] = await Promise.all([
        prisma.sync_queue_dlq_archive.findMany({
          where,
          skip,
          take: limit,
          orderBy: { archivedAt: 'desc' },
          select: {
            id: true,
            dlqId: true,
            originalId: true,
            table: true,
            operation: true,
            attempts: true,
            lastError: true,
            originalCreated: true,
            movedToDlqAt: true,
            archivedAt: true,
            resolution: true,
          },
        }),
        prisma.sync_queue_dlq_archive.count({ where }),
      ]);

      const totalPages = Math.ceil(total / limit);

      return {
        items,
        pagination: {
          page,
          limit,
          total,
          totalPages,
        },
      };
    } catch (error) {
      logger.error('Error listing archived DLQ items', error);
      throw new Error('Failed to list archived DLQ items');
    }
  },

  /**
   * Get unique tables from DLQ for filtering
   */
  async getDistinctTables(): Promise<string[]> {
    try {
      const tables = await prisma.sync_queue_dlq.groupBy({
        by: ['table'],
      });
      return tables.map(t => t.table).sort();
    } catch (error) {
      logger.error('Error getting distinct tables', error);
      return [];
    }
  },
};

export default dlqManagementService;
