/**
 * Async extraction job queue.
 *
 * For large batch requests, callers can submit an async job and poll for results.
 * Jobs are stored in-memory (Cosmos persistence deferred to Phase 7).
 *
 * Flow: POST /extract/jobs → { jobId } → GET /extract/jobs/:id → { status, results }
 * Optional: webhook callback on completion
 */

import { randomUUID } from 'node:crypto';
import {
  sidecarExtract,
  type SidecarExtractRequest,
  type SidecarExtractResponse,
} from '../../lib/python-bridge.js';
import { triggerJobWebhook, type WebhookConfig } from './webhooks.js';

export type JobStatus = 'pending' | 'processing' | 'completed' | 'failed';

export interface ExtractionJob {
  id: string;
  status: JobStatus;
  inputs: SidecarExtractRequest[];
  results: SidecarExtractResponse[];
  errors: Array<{ index: number; error: string }>;
  progress: { completed: number; total: number };
  createdAt: string;
  completedAt?: string;
  webhookConfig?: WebhookConfig;
}

const jobStore = new Map<string, ExtractionJob>();
const MAX_JOBS = 1000; // Prevent unbounded memory growth

/**
 * Cleanup old jobs to prevent memory leak.
 * Keeps most recent jobs, removes oldest completed/failed first.
 */
function cleanupOldJobs(): void {
  if (jobStore.size <= MAX_JOBS) return;

  const allJobs = [...jobStore.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));

  const toRemove = allJobs.slice(0, allJobs.length - MAX_JOBS);
  for (const job of toRemove) {
    // Only remove completed or failed jobs
    if (job.status === 'completed' || job.status === 'failed') {
      jobStore.delete(job.id);
    }
  }
}

/**
 * Create a new async extraction job and start processing in background.
 */
export function createJob(
  inputs: SidecarExtractRequest[],
  requestId?: string,
  webhookConfig?: WebhookConfig
): ExtractionJob {
  const job: ExtractionJob = {
    id: randomUUID(),
    status: 'pending',
    inputs,
    results: [],
    errors: [],
    progress: { completed: 0, total: inputs.length },
    createdAt: new Date().toISOString(),
    webhookConfig,
  };

  jobStore.set(job.id, job);

  // Cleanup old jobs to prevent memory leak
  cleanupOldJobs();

  // Start processing in background (non-blocking)
  processJob(job, requestId).catch(() => {
    job.status = 'failed';
  });

  return job;
}

/**
 * Get job by ID.
 */
export function getJob(jobId: string): ExtractionJob | undefined {
  return jobStore.get(jobId);
}

/**
 * List recent jobs (last 50).
 */
export function listJobs(limit = 50): ExtractionJob[] {
  return [...jobStore.values()]
    .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
    .slice(0, limit);
}

/**
 * Reset job store (for testing).
 */
export function resetJobStore(): void {
  jobStore.clear();
}

// ── Internal ─────────────────────────────────────────────────────

async function processJob(job: ExtractionJob, requestId?: string): Promise<void> {
  job.status = 'processing';

  for (let i = 0; i < job.inputs.length; i++) {
    try {
      const result = await sidecarExtract(job.inputs[i], requestId);
      job.results.push(result);
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Unknown error';
      job.errors.push({ index: i, error: message });
      // Push a placeholder so indices align
      job.results.push({
        extractions: [],
        metadata: { model_id: 'error', duration_ms: 0, char_count: 0 },
      });
    }
    job.progress.completed = i + 1;
  }

  job.status = job.errors.length === job.inputs.length ? 'failed' : 'completed';
  job.completedAt = new Date().toISOString();

  // Trigger webhook if configured
  if (job.webhookConfig) {
    await triggerJobWebhook(job, job.webhookConfig).catch(() => {
      // Webhook failures don't affect job status
    });
  }
}
