import { Prisma, type PrismaClient } from '@prisma/client';
import type { AIProgram } from './programSchema';

/**
 * Materialize a parsed AIProgram into a real Program row + nested
 * Weeks/Days/Exercises in one transaction.
 *
 * The caller is responsible for validating the AIProgram (parseAIProgram
 * returns it). Here we additionally:
 *   - verify every non-null exerciseId is in the user's library;
 *     reject the apply if any unknown ID is referenced (the UI must
 *     resolve unknowns BEFORE calling this).
 *   - set Program.aiGenerated = true and stash any extra notes.
 *
 * Returns the created Program's id.
 */
export interface ApplyOpts {
  userId: string;
  startDate: Date;
  /**
   * If true, mark Program.isActive = true. Default false (let the
   * user explicitly activate after reviewing).
   */
  isActive?: boolean;
}

export interface ApplyResult {
  programId: string;
  weeksCreated: number;
  daysCreated: number;
  exercisesCreated: number;
}

export async function applyAIProgram(
  prisma: PrismaClient,
  ai: AIProgram,
  opts: ApplyOpts,
): Promise<ApplyResult> {
  // Verify every exerciseId in the tree belongs to this user.
  const ids = new Set<string>();
  for (const w of ai.weeks)
    for (const d of w.days)
      for (const ex of d.exercises) {
        if (ex.exerciseId) ids.add(ex.exerciseId);
      }
  if (ids.size > 0) {
    const owned = await prisma.exercise.findMany({
      where: { userId: opts.userId, id: { in: Array.from(ids) } },
      select: { id: true },
    });
    const ownedIds = new Set(owned.map((e) => e.id));
    const bad = Array.from(ids).filter((id) => !ownedIds.has(id));
    if (bad.length > 0) {
      throw new Error(
        `Cannot apply: ${bad.length} exerciseId(s) don't belong to this user. ` +
          `Resolve unknown exercises in the preview first.`,
      );
    }
  }
  // Also reject if any exercise has exerciseId === null (unresolved).
  const unresolved: string[] = [];
  for (const w of ai.weeks)
    for (const d of w.days)
      for (const ex of d.exercises) {
        if (!ex.exerciseId) unresolved.push(ex.exerciseName);
      }
  if (unresolved.length > 0) {
    throw new Error(
      `Cannot apply: ${unresolved.length} exercise(s) still unresolved ` +
        `(${unresolved.slice(0, 3).join(', ')}${
          unresolved.length > 3 ? '...' : ''
        }). Map them to library exercises or remove them in the preview.`,
    );
  }

  let weeksCreated = 0;
  let daysCreated = 0;
  let exercisesCreated = 0;

  const programId = await prisma.$transaction(async (tx) => {
    const program = await tx.program.create({
      data: {
        userId: opts.userId,
        name: ai.name,
        description: ai.description ?? null,
        type: ai.type,
        durationWeeks: ai.durationWeeks,
        startDate: opts.startDate,
        isActive: opts.isActive ?? false,
        aiGenerated: true,
      },
    });

    for (const w of ai.weeks) {
      const week = await tx.programWeek.create({
        data: {
          programId: program.id,
          weekNumber: w.weekNumber,
          phase: w.phase ?? null,
          description: w.description ?? null,
        },
      });
      weeksCreated++;
      for (const d of w.days) {
        const day = await tx.programDay.create({
          data: {
            weekId: week.id,
            dayOfWeek: d.dayOfWeek,
            name: d.name ?? null,
            description: d.description ?? null,
          },
        });
        daysCreated++;
        if (d.exercises.length > 0) {
          await tx.programExercise.createMany({
            data: d.exercises.map((ex) => ({
              dayId: day.id,
              exerciseId: ex.exerciseId!, // validated non-null above
              order: ex.order,
              sets: ex.sets ?? null,
              repsMin: ex.repsMin ?? null,
              repsMax: ex.repsMax ?? null,
              rpe: ex.rpe ?? null,
              restSeconds: ex.restSeconds ?? null,
              suggestedWeight: ex.suggestedWeight ?? null,
              suggestedWeightUnit: ex.suggestedWeightUnit ?? null,
              notes: ex.notes ?? null,
            })) as Prisma.ProgramExerciseCreateManyInput[],
          });
          exercisesCreated += d.exercises.length;
        }
      }
    }
    return program.id;
  });

  return { programId, weeksCreated, daysCreated, exercisesCreated };
}
