import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { Prisma } from "@prisma/client";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { getProgramById } from "@/lib/db/programs";

/**
 * GET    /api/programs/[id]   — full program tree (weeks + days + exercises).
 * PATCH  /api/programs/[id]   — update metadata (name/description/type/
 *                                 durationWeeks/startDate/isActive) AND/OR
 *                                 replace the entire weeks tree if provided.
 *                                 Replace-tree is the simpler mental model
 *                                 for the UI editor + the AI apply flow:
 *                                 same payload shape as POST /api/programs.
 * DELETE /api/programs/[id]   — cascading delete (weeks/days/exercises go
 *                                 via Prisma onDelete: Cascade).
 */

const exerciseInput = z.object({
  exerciseId: z.string().min(1),
  order: z.number().int().nonnegative(),
  sets: z.number().int().positive().optional().nullable(),
  repsMin: z.number().int().positive().optional().nullable(),
  repsMax: z.number().int().positive().optional().nullable(),
  rpe: z.number().int().min(1).max(10).optional().nullable(),
  restSeconds: z.number().int().nonnegative().optional().nullable(),
  notes: z.string().optional().nullable(),
});
const dayInput = z.object({
  dayOfWeek: z.number().int().min(0).max(6),
  name: z.string().optional().nullable(),
  description: z.string().optional().nullable(),
  exercises: z.array(exerciseInput),
});
const weekInput = z.object({
  weekNumber: z.number().int().positive(),
  phase: z.string().optional().nullable(),
  description: z.string().optional().nullable(),
  days: z.array(dayInput),
});

const patchSchema = z.object({
  name: z.string().min(1).optional(),
  description: z.string().optional().nullable(),
  type: z.string().min(1).optional(),
  durationWeeks: z.number().int().positive().optional(),
  startDate: z.string().optional(),
  isActive: z.boolean().optional(),
  /** When provided, REPLACES the entire weeks tree. */
  weeks: z.array(weekInput).optional(),
});

export async function GET(
  _request: NextRequest,
  { params }: { params: { id: string } },
) {
  const user = await getCurrentUser();
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  const program = await getProgramById(user.id, params.id);
  if (!program) {
    return NextResponse.json({ error: "Program not found" }, { status: 404 });
  }
  return NextResponse.json(program);
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: { id: string } },
) {
  try {
    const user = await getCurrentUser();
    if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

    const existing = await prisma.program.findFirst({
      where: { id: params.id, userId: user.id },
      select: { id: true },
    });
    if (!existing) {
      return NextResponse.json({ error: "Program not found" }, { status: 404 });
    }

    const body = await request.json();
    const validated = patchSchema.parse(body);

    // If replacing the tree, verify exercise ownership.
    if (validated.weeks) {
      const allExerciseIds = new Set<string>();
      for (const w of validated.weeks)
        for (const d of w.days)
          for (const ex of d.exercises) allExerciseIds.add(ex.exerciseId);
      if (allExerciseIds.size > 0) {
        const owned = await prisma.exercise.findMany({
          where: { userId: user.id, id: { in: Array.from(allExerciseIds) } },
          select: { id: true },
        });
        const ownedIds = new Set(owned.map((e) => e.id));
        const bad = Array.from(allExerciseIds).filter((id) => !ownedIds.has(id));
        if (bad.length > 0) {
          return NextResponse.json(
            { error: "Some exerciseIds don't exist in your library", details: bad },
            { status: 400 },
          );
        }
      }
    }

    const programData: Prisma.ProgramUpdateInput = {};
    if (validated.name !== undefined) programData.name = validated.name;
    if (validated.description !== undefined)
      programData.description = validated.description;
    if (validated.type !== undefined) programData.type = validated.type;
    if (validated.durationWeeks !== undefined)
      programData.durationWeeks = validated.durationWeeks;
    if (validated.startDate !== undefined)
      programData.startDate = new Date(validated.startDate);
    if (validated.isActive !== undefined) programData.isActive = validated.isActive;

    await prisma.$transaction(async (tx) => {
      if (Object.keys(programData).length > 0) {
        await tx.program.update({ where: { id: params.id }, data: programData });
      }

      if (validated.weeks) {
        // Wipe and rebuild the entire tree. Cascading delete on
        // ProgramWeek removes ProgramDay + ProgramExercise; Workouts
        // referencing those days have their programDayId set to NULL
        // by the FK ON DELETE SET NULL clause we declared in the
        // schema, so adherence references aren't catastrophic.
        await tx.programWeek.deleteMany({ where: { programId: params.id } });
        for (const w of validated.weeks) {
          const week = await tx.programWeek.create({
            data: {
              programId: params.id,
              weekNumber: w.weekNumber,
              phase: w.phase ?? null,
              description: w.description ?? null,
            },
          });
          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,
              },
            });
            if (d.exercises.length > 0) {
              await tx.programExercise.createMany({
                data: d.exercises.map((ex) => ({
                  dayId: day.id,
                  exerciseId: ex.exerciseId,
                  order: ex.order,
                  sets: ex.sets ?? null,
                  repsMin: ex.repsMin ?? null,
                  repsMax: ex.repsMax ?? null,
                  rpe: ex.rpe ?? null,
                  restSeconds: ex.restSeconds ?? null,
                  notes: ex.notes ?? null,
                })) as Prisma.ProgramExerciseCreateManyInput[],
              });
            }
          }
        }
      }
    });

    const updated = await getProgramById(user.id, params.id);
    return NextResponse.json(updated);
  } catch (err) {
    if (err instanceof z.ZodError) {
      return NextResponse.json(
        { error: "Invalid patch payload", details: err.errors },
        { status: 400 },
      );
    }
    console.error("PATCH /api/programs/[id] error:", err);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}

export async function DELETE(
  _request: NextRequest,
  { params }: { params: { id: string } },
) {
  try {
    const user = await getCurrentUser();
    if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

    const existing = await prisma.program.findFirst({
      where: { id: params.id, userId: user.id },
      select: { id: true },
    });
    if (!existing) {
      return NextResponse.json({ error: "Program not found" }, { status: 404 });
    }
    await prisma.program.delete({ where: { id: params.id } });
    return NextResponse.json({ success: true });
  } catch (err) {
    console.error("DELETE /api/programs/[id] error:", err);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}
