/**
 * Payment Retry Queue System
 * Automatically retry failed payments with exponential backoff
 */

export interface PaymentRetryJob {
  id: string;
  transactionId: string;
  userId: number;
  amount: number;
  provider: string;
  status: 'pending' | 'processing' | 'completed' | 'failed' | 'abandoned';
  attempts: number;
  maxAttempts: number;
  nextRetryTime: Date;
  lastError?: string;
  createdAt: Date;
  updatedAt: Date;
  completedAt?: Date;
  metadata: Record<string, any>;
}

export interface RetrySchedule {
  attempt: number;
  delayMs: number;
  description: string;
}

// Default retry schedule: 24h, 48h, 72h
export const DEFAULT_RETRY_SCHEDULE: RetrySchedule[] = [
  { attempt: 1, delayMs: 24 * 60 * 60 * 1000, description: 'First retry after 24 hours' },
  { attempt: 2, delayMs: 48 * 60 * 60 * 1000, description: 'Second retry after 48 hours' },
  { attempt: 3, delayMs: 72 * 60 * 60 * 1000, description: 'Final retry after 72 hours' },
];

/**
 * Create a payment retry job
 */
export function createRetryJob(
  transactionId: string,
  userId: number,
  amount: number,
  provider: string,
  metadata: Record<string, any> = {}
): PaymentRetryJob {
  const now = new Date();
  const nextRetryTime = new Date(now.getTime() + DEFAULT_RETRY_SCHEDULE[0].delayMs);

  return {
    id: `RETRY-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
    transactionId,
    userId,
    amount,
    provider,
    status: 'pending',
    attempts: 0,
    maxAttempts: DEFAULT_RETRY_SCHEDULE.length,
    nextRetryTime,
    createdAt: now,
    updatedAt: now,
    metadata,
  };
}

/**
 * Get next retry time
 */
export function getNextRetryTime(job: PaymentRetryJob): Date {
  if (job.attempts >= job.maxAttempts) {
    return new Date(0); // No more retries
  }

  const schedule = DEFAULT_RETRY_SCHEDULE[job.attempts];
  const now = new Date();
  return new Date(now.getTime() + schedule.delayMs);
}

/**
 * Mark job as processing
 */
export function markAsProcessing(job: PaymentRetryJob): PaymentRetryJob {
  return {
    ...job,
    status: 'processing',
    updatedAt: new Date(),
  };
}

/**
 * Mark job as completed
 */
export function markAsCompleted(job: PaymentRetryJob): PaymentRetryJob {
  return {
    ...job,
    status: 'completed',
    completedAt: new Date(),
    updatedAt: new Date(),
  };
}

/**
 * Mark job as failed and schedule retry
 */
export function markAsFailed(
  job: PaymentRetryJob,
  error: string
): PaymentRetryJob {
  const attempts = job.attempts + 1;

  if (attempts >= job.maxAttempts) {
    return {
      ...job,
      status: 'abandoned',
      attempts,
      lastError: error,
      updatedAt: new Date(),
    };
  }

  const schedule = DEFAULT_RETRY_SCHEDULE[attempts];
  const nextRetryTime = new Date(Date.now() + schedule.delayMs);

  return {
    ...job,
    status: 'pending',
    attempts,
    nextRetryTime,
    lastError: error,
    updatedAt: new Date(),
  };
}

/**
 * Get jobs ready for retry
 */
export function getJobsReadyForRetry(jobs: PaymentRetryJob[]): PaymentRetryJob[] {
  const now = new Date();
  return jobs.filter(
    job =>
      job.status === 'pending' &&
      job.nextRetryTime <= now &&
      job.attempts < job.maxAttempts
  );
}

/**
 * Get job statistics
 */
export interface RetryQueueStats {
  total: number;
  pending: number;
  processing: number;
  completed: number;
  failed: number;
  abandoned: number;
  totalAmount: number;
  averageAttempts: number;
  successRate: number;
}

export function getQueueStats(jobs: PaymentRetryJob[]): RetryQueueStats {
  const pending = jobs.filter(j => j.status === 'pending').length;
  const processing = jobs.filter(j => j.status === 'processing').length;
  const completed = jobs.filter(j => j.status === 'completed').length;
  const failed = jobs.filter(j => j.status === 'failed').length;
  const abandoned = jobs.filter(j => j.status === 'abandoned').length;

  const totalAmount = jobs.reduce((sum, j) => sum + j.amount, 0);
  const averageAttempts = jobs.reduce((sum, j) => sum + j.attempts, 0) / (jobs.length || 1);
  const successRate = jobs.length > 0 ? (completed / jobs.length) * 100 : 0;

  return {
    total: jobs.length,
    pending,
    processing,
    completed,
    failed,
    abandoned,
    totalAmount,
    averageAttempts,
    successRate,
  };
}

/**
 * Generate retry report
 */
export function generateRetryReport(jobs: PaymentRetryJob[]): string {
  const stats = getQueueStats(jobs);
  const abandonedJobs = jobs.filter(j => j.status === 'abandoned');
  const completedJobs = jobs.filter(j => j.status === 'completed');

  return `
PAYMENT RETRY QUEUE REPORT
Generated: ${new Date().toISOString()}

QUEUE STATUS
------------
Total Jobs: ${stats.total}
Pending Retry: ${stats.pending}
Processing: ${stats.processing}
Completed: ${stats.completed}
Failed: ${stats.failed}
Abandoned: ${stats.abandoned}

METRICS
-------
Total Amount: $${stats.totalAmount.toFixed(2)}
Average Attempts: ${stats.averageAttempts.toFixed(1)}
Success Rate: ${stats.successRate.toFixed(1)}%

ABANDONED JOBS (${abandonedJobs.length})
${abandonedJobs.map(j => `- ${j.id}: User ${j.userId}, $${j.amount.toFixed(2)}, Error: ${j.lastError}`).join('\n')}

COMPLETED JOBS (${completedJobs.length})
${completedJobs.slice(-5).map(j => `- ${j.id}: User ${j.userId}, $${j.amount.toFixed(2)}, Attempts: ${j.attempts}`).join('\n')}

RECOMMENDATIONS
---------------
1. Review abandoned jobs for manual intervention
2. Contact users with abandoned payments
3. Investigate common error patterns
4. Consider adjusting retry schedule if needed
  `;
}

/**
 * Export retry queue to CSV
 */
export function exportRetryQueueToCSV(jobs: PaymentRetryJob[]): string {
  const headers = [
    'Retry ID',
    'Transaction ID',
    'User ID',
    'Amount',
    'Provider',
    'Status',
    'Attempts',
    'Next Retry',
    'Last Error',
    'Created At',
  ];

  const rows = jobs.map(j => [
    j.id,
    j.transactionId,
    j.userId,
    j.amount,
    j.provider,
    j.status,
    `${j.attempts}/${j.maxAttempts}`,
    j.nextRetryTime.toISOString(),
    j.lastError || 'N/A',
    j.createdAt.toISOString(),
  ]);

  const csv = [
    headers.join(','),
    ...rows.map(row => row.map(cell => `"${cell}"`).join(',')),
  ].join('\n');

  return csv;
}

/**
 * Cleanup old completed jobs
 */
export function cleanupCompletedJobs(
  jobs: PaymentRetryJob[],
  daysOld: number = 30
): PaymentRetryJob[] {
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - daysOld);

  return jobs.filter(j => {
    if (j.status !== 'completed' || !j.completedAt) {
      return true;
    }
    return j.completedAt > cutoffDate;
  });
}

/**
 * Get retry job by transaction ID
 */
export function getRetryJobByTransactionId(
  jobs: PaymentRetryJob[],
  transactionId: string
): PaymentRetryJob | undefined {
  return jobs.find(j => j.transactionId === transactionId);
}

/**
 * Get retry jobs by user
 */
export function getRetryJobsByUser(
  jobs: PaymentRetryJob[],
  userId: number
): PaymentRetryJob[] {
  return jobs.filter(j => j.userId === userId);
}

/**
 * Get retry jobs by status
 */
export function getRetryJobsByStatus(
  jobs: PaymentRetryJob[],
  status: PaymentRetryJob['status']
): PaymentRetryJob[] {
  return jobs.filter(j => j.status === status);
}

/**
 * Get retry jobs by provider
 */
export function getRetryJobsByProvider(
  jobs: PaymentRetryJob[],
  provider: string
): PaymentRetryJob[] {
  return jobs.filter(j => j.provider === provider);
}

/**
 * Calculate retry success probability
 */
export function calculateSuccessProbability(job: PaymentRetryJob): number {
  // Probability decreases with each attempt
  const baseProbability = 0.7; // 70% base success rate
  const attemptPenalty = job.attempts * 0.1; // 10% penalty per attempt
  const probability = Math.max(0.1, baseProbability - attemptPenalty);

  return probability * 100;
}

/**
 * Get recommended action for retry job
 */
export function getRecommendedAction(job: PaymentRetryJob): string {
  if (job.status === 'abandoned') {
    return 'Contact user and offer alternative payment methods';
  }

  if (job.status === 'completed') {
    return 'Job completed successfully';
  }

  if (job.attempts >= 2) {
    return 'Consider manual intervention or alternative payment method';
  }

  if (job.lastError?.includes('insufficient')) {
    return 'User may have insufficient funds - contact for update';
  }

  if (job.lastError?.includes('declined')) {
    return 'Card was declined - user should verify card details';
  }

  return 'Automatic retry will be attempted at scheduled time';
}

/**
 * Batch retry jobs
 */
export function batchRetryJobs(
  jobs: PaymentRetryJob[],
  batchSize: number = 100
): PaymentRetryJob[][] {
  const batches: PaymentRetryJob[][] = [];

  for (let i = 0; i < jobs.length; i += batchSize) {
    batches.push(jobs.slice(i, i + batchSize));
  }

  return batches;
}

/**
 * Calculate queue health
 */
export interface QueueHealth {
  status: 'healthy' | 'warning' | 'critical';
  score: number; // 0-100
  issues: string[];
}

export function calculateQueueHealth(jobs: PaymentRetryJob[]): QueueHealth {
  const stats = getQueueStats(jobs);
  const issues: string[] = [];
  let score = 100;

  // Check for high abandoned rate
  if (stats.abandoned > stats.completed) {
    issues.push('High abandoned job rate');
    score -= 20;
  }

  // Check for stalled processing
  const processingJobs = jobs.filter(j => j.status === 'processing');
  if (processingJobs.length > 10) {
    issues.push('Many jobs stuck in processing state');
    score -= 15;
  }

  // Check for old pending jobs
  const oldPendingJobs = jobs.filter(
    j =>
      j.status === 'pending' &&
      Date.now() - j.createdAt.getTime() > 7 * 24 * 60 * 60 * 1000
  );
  if (oldPendingJobs.length > 5) {
    issues.push('Old pending jobs not being retried');
    score -= 10;
  }

  // Check success rate
  if (stats.successRate < 50) {
    issues.push('Low success rate - investigate payment issues');
    score -= 25;
  }

  let status: 'healthy' | 'warning' | 'critical' = 'healthy';
  if (score < 50) {
    status = 'critical';
  } else if (score < 75) {
    status = 'warning';
  }

  return {
    status,
    score: Math.max(0, score),
    issues,
  };
}
