import { v4 as uuid } from 'uuid';
import { getPocketBase } from '../lib/pocketbase.js';
import { generateMicroLearning } from '../pipeline/generate.js';
import {
  MICRO_LEARNING_TYPES,
  type GenerationJob,
  type JobProgress,
  type TopicRecord,
} from '../types.js';

// ---------------------------------------------------------------------------
// In-memory store
// ---------------------------------------------------------------------------

const jobs = new Map<string, GenerationJob>();

const DEFAULT_PROGRESS: JobProgress = {
  topicsTotal: 0,
  topicsProcessed: 0,
  itemsTotal: 0,
  itemsGenerated: 0,
  itemsFailed: 0,
};

export function createJob(themeId: string): GenerationJob {
  const job: GenerationJob = {
    id: uuid(),
    themeId,
    status: 'queued',
    progress: { ...DEFAULT_PROGRESS },
    error: null,
    createdAt: new Date(),
    updatedAt: new Date(),
  };
  jobs.set(job.id, job);
  void runPipeline(job.id);
  return job;
}

export function getJob(id: string): GenerationJob | undefined {
  return jobs.get(id);
}

function updateJob(id: string, updates: Partial<Omit<GenerationJob, 'id' | 'createdAt'>>): void {
  const job = jobs.get(id);
  if (!job) return;
  jobs.set(id, { ...job, ...updates, updatedAt: new Date() });
}

function mergeProgress(id: string, partial: Partial<JobProgress>): void {
  const job = jobs.get(id);
  if (!job) return;
  updateJob(id, { progress: { ...job.progress, ...partial } });
}

// ---------------------------------------------------------------------------
// Pipeline orchestration
// ---------------------------------------------------------------------------

async function runPipeline(jobId: string): Promise<void> {
  const job = jobs.get(jobId);
  if (!job) return;

  updateJob(jobId, { status: 'running' });

  let topics: TopicRecord[];
  try {
    topics = await fetchPublishedTopics(job.themeId);
  } catch (err) {
    const reason = err instanceof Error ? err.message : String(err);
    updateJob(jobId, { status: 'failed', error: `topic_fetch_failed: ${reason}` });
    return;
  }

  const totalItems = topics.length * MICRO_LEARNING_TYPES.length;
  mergeProgress(jobId, {
    topicsTotal: topics.length,
    itemsTotal: totalItems,
  });

  // Pre-create all micro_learning records with status: queued
  const recordIds = await preCreateRecords(topics);

  let generated = 0;
  let failed = 0;

  for (let ti = 0; ti < topics.length; ti++) {
    const topic = topics[ti];
    if (!topic) continue;

    for (const type of MICRO_LEARNING_TYPES) {
      const recordId = recordIds.get(`${topic.id}:${type}`);
      if (!recordId) continue;

      try {
        const content = await generateMicroLearning(topic, type);
        await updateMicroLearning(recordId, 'generated', content);
        generated++;
      } catch (err) {
        const reason = err instanceof Error ? err.message : String(err);
        await updateMicroLearning(recordId, 'failed', null, reason);
        failed++;
      }

      mergeProgress(jobId, { itemsGenerated: generated, itemsFailed: failed });
    }

    mergeProgress(jobId, { topicsProcessed: ti + 1 });
  }

  updateJob(jobId, { status: 'done' });
}

// ---------------------------------------------------------------------------
// PocketBase helpers
// ---------------------------------------------------------------------------

async function fetchPublishedTopics(themeId: string): Promise<TopicRecord[]> {
  const pb = await getPocketBase();
  const records = await pb.collection('topics').getFullList({
    filter: `theme = "${themeId}" && status = "published"`,
  });

  return records.map(r => ({
    id: r['id'] as string,
    title: r['title'] as string,
    body: r['body'] as string,
    difficulty: r['difficulty'] as TopicRecord['difficulty'],
    key_terms: (r['key_terms'] as string[] | null) ?? [],
    status: r['status'] as string,
  }));
}

async function preCreateRecords(
  topics: TopicRecord[],
): Promise<Map<string, string>> {
  const pb = await getPocketBase();
  const map = new Map<string, string>();

  for (const topic of topics) {
    for (const type of MICRO_LEARNING_TYPES) {
      const record = await pb.collection('micro_learnings').create({
        topic: topic.id,
        type,
        content: null,
        status: 'queued',
        generation_model: 'claude-sonnet-4-20250514',
      });
      map.set(`${topic.id}:${type}`, record.id);
    }
  }

  return map;
}

async function updateMicroLearning(
  recordId: string,
  status: 'generated' | 'failed',
  content: unknown,
  _errorNote?: string,
): Promise<void> {
  const pb = await getPocketBase();
  await pb.collection('micro_learnings').update(recordId, {
    status,
    content: content ?? null,
    generated_at: status === 'generated' ? new Date().toISOString() : null,
  });
}
