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';

/**
 * POST /api/me/import
 *
 * Restore a previously-exported account dump (from /api/me/export) into
 * the CURRENT user's account. Use cases:
 *   - Move from one Proof of Work instance to another (export there,
 *     import here).
 *   - Disaster recovery from a known-good export file when the live
 *     /data is gone.
 *
 * Behavior:
 *   - Mode 'merge' (default): existing data stays put. Imported
 *     exercises with names that already exist for this user are
 *     skipped (no overwrite); workouts and sets are always added as
 *     new rows with fresh IDs.
 *   - Mode 'replace': hard-deletes everything the current user owns
 *     (workouts, sets, exercises, programs, preferences) before
 *     inserting the imported data. Profile + admin flag are
 *     preserved. Requires `confirm: "REPLACE"` in the body.
 *
 * What is NOT imported:
 *   - User identity (email, password, isAdmin) — the rows always go to
 *     the actor's id.
 *   - Sessions — never round-tripped; an import shouldn't grant new
 *     login tokens.
 *   - InstanceSettings — instance-wide, not account-owned.
 *
 * The export schema string is checked: only `proof-of-work-export@1`
 * is accepted today. Future schema bumps must add migration code here.
 */

const exerciseImport = z.object({
  name: z.string().min(1),
  description: z.string().nullable().optional(),
  type: z.string(),
  muscleGroups: z.string(), // already JSON-stringified in export
  inputFields: z.string().optional(),
  defaultWeightUnit: z.string().nullable().optional(),
  isCustom: z.boolean().optional(),
});

const setLogImport = z.object({
  setNumber: z.number().int().positive(),
  reps: z.number().int().nullable().optional(),
  weight: z.number().nullable().optional(),
  weightUnit: z.string().optional(),
  rpe: z.number().int().nullable().optional(),
  durationSeconds: z.number().int().nullable().optional(),
  distance: z.number().nullable().optional(),
  distanceUnit: z.string().nullable().optional(),
  calories: z.number().int().nullable().optional(),
  customMetrics: z.string().nullable().optional(),
  notes: z.string().nullable().optional(),
  // The exported set carries an exerciseId pointing into the export's
  // own exercise list, but NOT a stable id we can FK on after import.
  // We re-resolve via the parent workout's exercise-name lookup below.
  exerciseId: z.string(),
});

const workoutImport = z.object({
  date: z.string(), // ISO
  name: z.string().nullable().optional(),
  notes: z.string().nullable().optional(),
  durationMinutes: z.number().int().nullable().optional(),
  difficulty: z.number().int().nullable().optional(),
  caloriesBurned: z.number().int().nullable().optional(),
  setLogs: z.array(setLogImport),
});

const importPayload = z.object({
  schema: z.literal('proof-of-work-export@1'),
  exercises: z.array(exerciseImport),
  workouts: z.array(workoutImport),
});

const requestBody = z.object({
  payload: importPayload,
  mode: z.enum(['merge', 'replace']).default('merge'),
  confirm: z.string().optional(),
});

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

    const body = await request.json();
    const parsed = requestBody.safeParse(body);
    if (!parsed.success) {
      return NextResponse.json(
        { error: 'Invalid import payload', details: parsed.error.errors },
        { status: 400 },
      );
    }
    const { payload, mode, confirm } = parsed.data;

    if (mode === 'replace' && confirm !== 'REPLACE') {
      return NextResponse.json(
        {
          error:
            'Replace mode requires `confirm: "REPLACE"` in the request body. This deletes every workout/exercise/program owned by your account before importing.',
        },
        { status: 400 },
      );
    }

    // Build an export-id -> exercise-name map so set logs can be
    // rebound to the freshly-created exercises after merge/replace.
    // The export's `id` field is on each row but Zod's exercise schema
    // doesn't include id (we don't need it as a column to insert).
    // Read it from the raw payload to preserve the FK linkage between
    // the export's setLogs.exerciseId and its exercises[].id.
    const exportExerciseIdToName = new Map<string, string>();
    const rawExercises = (body?.payload?.exercises ?? []) as Array<
      Record<string, unknown>
    >;
    for (const ex of rawExercises) {
      if (typeof ex?.id === 'string' && typeof ex?.name === 'string') {
        exportExerciseIdToName.set(ex.id, ex.name);
      }
    }

    const summary = await prisma.$transaction(
      async (tx) => {
        if (mode === 'replace') {
          // Order matters because of FK cascades — explicit deletes
          // are still cleanest to avoid relying on cascade semantics.
          await tx.setLog.deleteMany({
            where: { workout: { userId: user.id } },
          });
          await tx.workout.deleteMany({ where: { userId: user.id } });
          await tx.exercise.deleteMany({ where: { userId: user.id } });
        }

        // ---- Exercises: upsert by (userId, name) -------------------
        const nameToNewId = new Map<string, string>();
        let exercisesCreated = 0;
        let exercisesSkipped = 0;
        for (const ex of payload.exercises) {
          const existing = await tx.exercise.findUnique({
            where: { userId_name: { userId: user.id, name: ex.name } },
            select: { id: true },
          });
          if (existing) {
            nameToNewId.set(ex.name, existing.id);
            exercisesSkipped++;
            continue;
          }
          const created = await tx.exercise.create({
            data: {
              userId: user.id,
              name: ex.name,
              description: ex.description ?? null,
              type: ex.type,
              muscleGroups: ex.muscleGroups,
              inputFields: ex.inputFields ?? '["sets","reps","weight"]',
              defaultWeightUnit: ex.defaultWeightUnit ?? null,
              isCustom: ex.isCustom ?? false,
            } as Prisma.ExerciseUncheckedCreateInput,
            select: { id: true, name: true },
          });
          nameToNewId.set(created.name, created.id);
          exercisesCreated++;
        }

        // ---- Workouts + sets ---------------------------------------
        let workoutsCreated = 0;
        let setsCreated = 0;
        let setsSkipped = 0;
        for (const w of payload.workouts) {
          const setData = [];
          for (const s of w.setLogs) {
            const exerciseName = exportExerciseIdToName.get(s.exerciseId);
            const targetId = exerciseName
              ? nameToNewId.get(exerciseName)
              : undefined;
            if (!targetId) {
              setsSkipped++;
              continue;
            }
            setData.push({
              exerciseId: targetId,
              setNumber: s.setNumber,
              reps: s.reps ?? null,
              weight: s.weight ?? null,
              weightUnit: s.weightUnit ?? 'lbs',
              rpe: s.rpe ?? null,
              durationSeconds: s.durationSeconds ?? null,
              distance: s.distance ?? null,
              distanceUnit: s.distanceUnit ?? null,
              calories: s.calories ?? null,
              customMetrics: s.customMetrics ?? null,
              notes: s.notes ?? null,
            });
          }
          await tx.workout.create({
            data: {
              userId: user.id,
              date: new Date(w.date),
              name: w.name ?? null,
              notes: w.notes ?? null,
              durationMinutes: w.durationMinutes ?? null,
              difficulty: w.difficulty ?? null,
              caloriesBurned: w.caloriesBurned ?? null,
              setLogs: setData.length > 0 ? { create: setData } : undefined,
            } as Prisma.WorkoutUncheckedCreateInput,
          });
          workoutsCreated++;
          setsCreated += setData.length;
        }

        return {
          mode,
          exercisesCreated,
          exercisesSkipped,
          workoutsCreated,
          setsCreated,
          setsSkipped,
        };
      },
      { timeout: 60_000 },
    );

    return NextResponse.json(summary);
  } catch (err) {
    console.error('POST /api/me/import error:', err);
    return NextResponse.json(
      { error: (err as Error).message ?? 'Internal server error' },
      { status: 500 },
    );
  }
}
