import { getPocketBase } from '../lib/pocketbase.js';
import { anthropic, MODELS } from '../lib/anthropic.js';
import { sequenceTopics } from './sequence.js';
import { buildCycleSystemRules, buildCycleUserPrompt } from './cycle.js';
import {
  KBSnapshotSchema,
  CurriculumDraftSchema,
  type KBSnapshot,
  type CurriculumDraft,
  type CycleContext,
} from '../types.js';

const BASE_SYSTEM_PROMPT = `You are a curriculum designer. Your task is to distribute a set of learning
Themes across 26 weekly sessions to create an effective learning journey.

Output ONLY valid JSON matching the schema provided. No preamble, no
explanation, no markdown fences.

Rules:
- Every Theme must appear at least once across 26 weeks
- Themes with more Topics (higher topic count) may span multiple weeks or
  appear in multiple cycles within the 26 weeks
- Sequence Themes so foundational concepts precede dependent ones
- Distribute complexity progressively: introductory Themes early, advanced
  Themes in the second half
- If total Topics across all Themes exceeds what 26 weeks can cover in depth,
  prioritise breadth in cycle 1 — every Theme covered, key Topics per Theme
- Assign an estimated duration in minutes per week (15–45 minutes per session)
- Return exactly 26 week slots

Output schema:
{
  "weeks": [
    {
      "weekNumber": 1,
      "themeId": "string",
      "topicIds": ["string"],
      "estimatedDurationMinutes": 25,
      "rationale": "one sentence"
    }
  ]
}`;

export async function fetchKBSnapshot(): Promise<KBSnapshot> {
  const pb = await getPocketBase();

  const themeRecords = await pb.collection('themes').getFullList({
    filter: 'status = "published"',
    expand: 'topics',
  });

  const themes = themeRecords.map(theme => {
    const expandedTopics = (theme['expand'] as Record<string, unknown>)?.['topics'];
    const rawTopics = Array.isArray(expandedTopics) ? expandedTopics : [];

    const topics = rawTopics.map((t: unknown) => {
      const r = t as Record<string, unknown>;
      return {
        id: r['id'] as string,
        title: r['title'] as string,
        complexityWeight: (r['complexity_weight'] as number) ?? 1,
        difficulty: (r['difficulty'] as string) ?? 'introductory',
        prerequisiteTopics: (r['prerequisite_topics'] as string[]) ?? [],
        relatedTopics: (r['related_topics'] as string[]) ?? [],
        contrastTopics: (r['contrast_topics'] as string[]) ?? [],
      };
    });

    return {
      id: theme['id'] as string,
      title: theme['title'] as string,
      description: (theme['description'] as string) ?? '',
      topics,
    };
  });

  return KBSnapshotSchema.parse({ themes });
}

export function preprocessSnapshot(snapshot: KBSnapshot): KBSnapshot {
  return {
    themes: snapshot.themes.map(theme => ({
      ...theme,
      topics: sequenceTopics(theme.topics),
    })),
  };
}

async function callAI(
  systemPrompt: string,
  userPrompt: string,
): Promise<CurriculumDraft> {
  for (let attempt = 0; attempt < 2; attempt++) {
    const message = await anthropic.messages.create({
      model: MODELS.SONNET,
      max_tokens: 4000,
      temperature: 0,
      system: systemPrompt,
      messages: [{ role: 'user', content: userPrompt }],
    });

    const textBlock = message.content.find(b => b.type === 'text');
    if (!textBlock || textBlock.type !== 'text') {
      if (attempt === 0) continue;
      throw new Error('No text block in AI response');
    }

    let parsed: unknown;
    try {
      parsed = JSON.parse(textBlock.text);
    } catch {
      if (attempt === 0) continue;
      throw new Error('AI response is not valid JSON');
    }

    const result = CurriculumDraftSchema.safeParse(parsed);
    if (!result.success) {
      if (attempt === 0) continue;
      throw new Error(`AI response failed Zod validation: ${result.error.message}`);
    }

    return result.data;
  }

  throw new Error('AI generation failed after 2 attempts');
}

function validateDraftAgainstSnapshot(draft: CurriculumDraft, snapshot: KBSnapshot): void {
  const themeIds = new Set(snapshot.themes.map(t => t.id));
  const topicIds = new Set(snapshot.themes.flatMap(t => t.topics.map(p => p.id)));

  for (const week of draft.weeks) {
    if (!themeIds.has(week.themeId)) {
      throw new Error(`Unknown themeId in week ${week.weekNumber}: ${week.themeId}`);
    }
    for (const topicId of week.topicIds) {
      if (!topicIds.has(topicId)) {
        throw new Error(`Unknown topicId in week ${week.weekNumber}: ${topicId}`);
      }
    }
  }
}

export async function writeDraftToPocketBase(
  draft: CurriculumDraft,
  reason: string,
): Promise<string> {
  const pb = await getPocketBase();

  // Determine next version number
  const existing = await pb.collection('curriculum_versions').getFullList({
    sort: '-version',
    perPage: 1,
  });
  const latestVersion = existing[0] ? (existing[0]['version'] as number) : 0;

  const versionRecord = await pb.collection('curriculum_versions').create({
    version: latestVersion + 1,
    status: 'draft',
    generated_at: new Date().toISOString(),
    generation_notes: reason,
  });

  for (const week of draft.weeks) {
    await pb.collection('curriculum_weeks').create({
      curriculum_version: versionRecord.id,
      week_number: week.weekNumber,
      theme: week.themeId,
      topics: week.topicIds,
      topic_order: week.topicIds.map((_, i) => i),
      estimated_duration_minutes: week.estimatedDurationMinutes,
      admin_notes: week.rationale,
    });
  }

  return versionRecord.id;
}

export async function buildCurriculum(
  reason: string,
  cycleCtx?: CycleContext,
): Promise<string> {
  const rawSnapshot = await fetchKBSnapshot();
  const snapshot = preprocessSnapshot(rawSnapshot);

  let systemPrompt = BASE_SYSTEM_PROMPT;
  let userPrompt: string;

  if (cycleCtx && cycleCtx.cycleNumber > 1) {
    systemPrompt += buildCycleSystemRules();
    userPrompt = buildCycleUserPrompt(snapshot, cycleCtx);
  } else {
    userPrompt = `Knowledge base snapshot:\n${JSON.stringify(snapshot)}\n\nGenerate a 26-week curriculum schedule.`;
  }

  const draft = await callAI(systemPrompt, userPrompt);
  validateDraftAgainstSnapshot(draft, snapshot);

  return writeDraftToPocketBase(draft, reason);
}
