import { query } from '../db';

// Exponential backoff with jitter
export async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  opts?: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
  }
): Promise<T> {
  const maxRetries = opts?.maxRetries ?? 3;
  const baseDelay = opts?.baseDelay ?? 1000;
  const maxDelay = opts?.maxDelay ?? 30000;

  let lastError: Error | undefined;
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastError = err instanceof Error ? err : new Error(String(err));
      if (attempt === maxRetries) break;

      const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
      const jitter = delay * (0.5 + Math.random() * 0.5);
      await new Promise(resolve => setTimeout(resolve, jitter));
    }
  }
  throw lastError;
}

// Circuit breaker with DB-persisted state
type CBState = 'closed' | 'open' | 'half-open';

export class CircuitBreaker {
  private name: string;
  private failureThreshold: number;
  private cooldownMs: number;

  // In-memory cache to avoid constant DB reads
  private cachedState: CBState = 'closed';
  private cachedFailureCount = 0;
  private lastTrippedAt: number | null = null;

  constructor(name: string, opts?: { failureThreshold?: number; cooldownMs?: number }) {
    this.name = name;
    this.failureThreshold = opts?.failureThreshold ?? 5;
    this.cooldownMs = opts?.cooldownMs ?? 300000;
  }

  async loadState(): Promise<void> {
    try {
      const result = await query(
        `SELECT * FROM sc_circuit_breaker_state WHERE id = $1`,
        [this.name]
      );
      if (result.rows.length > 0) {
        const row = result.rows[0];
        this.cachedState = row.state as CBState;
        this.cachedFailureCount = row.failure_count;
        this.lastTrippedAt = row.tripped_at ? new Date(row.tripped_at).getTime() : null;
      } else {
        await query(
          `INSERT INTO sc_circuit_breaker_state (id) VALUES ($1) ON CONFLICT (id) DO NOTHING`,
          [this.name]
        );
      }
    } catch {
      // Default to closed if DB unavailable
    }
  }

  private async persistState(): Promise<void> {
    try {
      await query(
        `INSERT INTO sc_circuit_breaker_state (id, state, failure_count, tripped_at, updated_at)
         VALUES ($1, $2, $3, $4, NOW())
         ON CONFLICT (id) DO UPDATE SET
           state = $2, failure_count = $3, tripped_at = $4, updated_at = NOW()`,
        [this.name, this.cachedState, this.cachedFailureCount,
         this.lastTrippedAt ? new Date(this.lastTrippedAt) : null]
      );
    } catch {
      // Best effort persistence
    }
  }

  getState(): CBState {
    if (this.cachedState === 'open' && this.lastTrippedAt) {
      if (Date.now() - this.lastTrippedAt > this.cooldownMs) {
        this.cachedState = 'half-open';
      }
    }
    return this.cachedState;
  }

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    const state = this.getState();

    if (state === 'open') {
      throw new Error(`Circuit breaker "${this.name}" is OPEN — request blocked`);
    }

    try {
      const result = await fn();
      // Success: reset on half-open, or just record success
      if (state === 'half-open') {
        this.cachedState = 'closed';
        this.cachedFailureCount = 0;
        this.lastTrippedAt = null;
      }
      await query(
        `UPDATE sc_circuit_breaker_state SET last_success_at = NOW() WHERE id = $1`,
        [this.name]
      ).catch(() => {});
      await this.persistState();
      return result;
    } catch (err) {
      this.cachedFailureCount++;
      await query(
        `UPDATE sc_circuit_breaker_state SET last_failure_at = NOW() WHERE id = $1`,
        [this.name]
      ).catch(() => {});

      if (this.cachedFailureCount >= this.failureThreshold || state === 'half-open') {
        this.cachedState = 'open';
        this.lastTrippedAt = Date.now();
      }
      await this.persistState();
      throw err;
    }
  }
}
