import { prisma } from "../prisma";

/**
 * Server-side helpers for the Programs feature (v1.1.0:1).
 *
 * A "program" is a multi-week training plan: Program -> ProgramWeek
 * -> ProgramDay -> ProgramExercise. The user follows it day-by-day,
 * logging actual workouts that get tagged with `Workout.programDayId`
 * so we can later compute adherence.
 */

export async function getPrograms(userId: string) {
  return prisma.program.findMany({
    where: { userId },
    orderBy: [{ isActive: "desc" }, { createdAt: "desc" }],
    include: {
      _count: { select: { weeks: true } },
    },
  });
}

export async function getProgramById(userId: string, programId: string) {
  return prisma.program.findFirst({
    where: { id: programId, userId },
    include: {
      weeks: {
        orderBy: { weekNumber: "asc" },
        include: {
          days: {
            orderBy: { dayOfWeek: "asc" },
            include: {
              exercises: {
                orderBy: { order: "asc" },
                include: { exercise: true },
              },
            },
          },
        },
      },
    },
  });
}

export async function getActivePrograms(userId: string) {
  return prisma.program.findMany({
    where: { userId, isActive: true },
    include: {
      weeks: {
        orderBy: { weekNumber: "asc" },
        include: {
          days: {
            orderBy: { dayOfWeek: "asc" },
            include: {
              exercises: {
                orderBy: { order: "asc" },
                include: { exercise: true },
              },
            },
          },
        },
      },
    },
  });
}

/**
 * For an active program + a target date, compute which planned
 * ProgramDay (if any) is "today's session." Algorithm:
 *
 *   daysSinceStart = floor((today - startDate) / 1 day)
 *   weekNumber     = floor(daysSinceStart / 7) + 1
 *   dayOfWeek      = today.getDay()  (0=Sun..6=Sat)
 *
 * Then look up the ProgramDay matching (weekNumber, dayOfWeek). If
 * the program has no day for today's dayOfWeek, returns null (rest
 * day or program doesn't cover this slot).
 *
 * Returns null if:
 *  - the date is before the program's startDate
 *  - the date is past durationWeeks * 7 days from the start
 *  - the program has no matching day for that (weekNumber, dayOfWeek)
 *
 * The date comparison is anchored to UTC midnight on both sides so
 * timezone fuzz doesn't shift you across day boundaries.
 */
export interface TodaysSession {
  program: {
    id: string;
    name: string;
    type: string;
    durationWeeks: number;
    startDate: Date;
  };
  weekNumber: number;
  dayOfWeek: number;
  day: NonNullable<
    Awaited<ReturnType<typeof getProgramById>>
  >["weeks"][number]["days"][number] | null;
}

export function computeTodaysSessionForProgram(
  program: NonNullable<Awaited<ReturnType<typeof getProgramById>>>,
  today: Date,
): TodaysSession | null {
  const startUtc = Date.UTC(
    program.startDate.getUTCFullYear(),
    program.startDate.getUTCMonth(),
    program.startDate.getUTCDate(),
  );
  const todayUtc = Date.UTC(
    today.getUTCFullYear(),
    today.getUTCMonth(),
    today.getUTCDate(),
  );

  const dayMs = 24 * 60 * 60 * 1000;
  const daysSinceStart = Math.floor((todayUtc - startUtc) / dayMs);

  if (daysSinceStart < 0) return null; // hasn't started yet
  if (daysSinceStart >= program.durationWeeks * 7) return null; // program over

  const weekNumber = Math.floor(daysSinceStart / 7) + 1;
  const dayOfWeek = today.getUTCDay();

  const week = program.weeks.find((w) => w.weekNumber === weekNumber);
  const day = week?.days.find((d) => d.dayOfWeek === dayOfWeek) ?? null;

  return {
    program: {
      id: program.id,
      name: program.name,
      type: program.type,
      durationWeeks: program.durationWeeks,
      startDate: program.startDate,
    },
    weekNumber,
    dayOfWeek,
    day,
  };
}

/**
 * Resolve the user's "today's session" across all active programs.
 * If multiple programs are active and have a session today, returns
 * the most-recently-started one (only the first match in practice
 * since active programs are usually unique).
 */
export async function getTodaysSession(
  userId: string,
  today: Date = new Date(),
): Promise<TodaysSession | null> {
  const programs = await getActivePrograms(userId);
  for (const p of programs.sort(
    (a, b) => b.startDate.getTime() - a.startDate.getTime(),
  )) {
    const session = computeTodaysSessionForProgram(p, today);
    if (session?.day) return session;
  }
  return null;
}
