import { describe, it, expect, beforeEach, vi } from 'vitest';

const { getCurrentUserMock } = vi.hoisted(() => ({
  getCurrentUserMock: vi.fn(),
}));
vi.mock('@/lib/auth', async (orig) => {
  const actual = (await orig()) as Record<string, unknown>;
  return { ...actual, getCurrentUser: getCurrentUserMock };
});
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }));

import { NextRequest } from 'next/server';
import { prisma } from '@/lib/prisma';
import { POST as importPost } from '@/app/api/me/import/route';

function jsonReq(body: unknown): NextRequest {
  return new NextRequest('http://x/api/me/import', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(body),
  } as ConstructorParameters<typeof NextRequest>[1]);
}

async function makeUser(opts: { email: string }) {
  return prisma.user.create({
    data: { email: opts.email, passwordHash: 'fake', isAdmin: false },
  });
}

beforeEach(async () => {
  await prisma.session.deleteMany();
  await prisma.exercise.deleteMany();
  await prisma.workout.deleteMany();
  await prisma.user.deleteMany();
  await prisma.instanceSettings.deleteMany();
  getCurrentUserMock.mockReset();
});

const sampleExport = (overrides?: Partial<{ exercises: unknown[]; workouts: unknown[] }>) => ({
  schema: 'proof-of-work-export@1',
  exercises: overrides?.exercises ?? [
    {
      id: 'cE1',
      name: 'Imported Bench',
      type: 'barbell',
      muscleGroups: '["chest"]',
      inputFields: '["sets","reps","weight"]',
      defaultWeightUnit: null,
      isCustom: false,
    },
    {
      id: 'cE2',
      name: 'Imported Squat',
      type: 'barbell',
      muscleGroups: '["legs"]',
      inputFields: '["sets","reps","weight"]',
      defaultWeightUnit: null,
      isCustom: false,
    },
  ],
  workouts: overrides?.workouts ?? [
    {
      date: '2026-04-01T12:00:00.000Z',
      name: 'Day 1',
      setLogs: [
        { exerciseId: 'cE1', setNumber: 1, reps: 5, weight: 225 },
        { exerciseId: 'cE1', setNumber: 2, reps: 5, weight: 245 },
      ],
    },
    {
      date: '2026-04-03T12:00:00.000Z',
      name: 'Day 2',
      setLogs: [
        { exerciseId: 'cE2', setNumber: 1, reps: 5, weight: 315 },
      ],
    },
  ],
});

describe('POST /api/me/import', () => {
  it('returns 401 unauthenticated', async () => {
    getCurrentUserMock.mockResolvedValue(null);
    const res = await importPost(jsonReq({ payload: sampleExport() }));
    expect(res.status).toBe(401);
  });

  it('rejects unknown schema versions', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);
    const res = await importPost(
      jsonReq({
        payload: { schema: 'something-else@99', exercises: [], workouts: [] },
      }),
    );
    expect(res.status).toBe(400);
  });

  it('merge mode imports exercises and workouts attributed to the actor', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);
    const res = await importPost(jsonReq({ payload: sampleExport() }));
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.exercisesCreated).toBe(2);
    expect(body.workoutsCreated).toBe(2);
    expect(body.setsCreated).toBe(3);

    const exercises = await prisma.exercise.findMany({
      where: { userId: u.id },
    });
    expect(exercises.map((e) => e.name).sort()).toEqual([
      'Imported Bench',
      'Imported Squat',
    ]);
    const workouts = await prisma.workout.findMany({
      where: { userId: u.id },
      include: { setLogs: true },
    });
    expect(workouts).toHaveLength(2);
    const totalSets = workouts.reduce((n, w) => n + w.setLogs.length, 0);
    expect(totalSets).toBe(3);
  });

  it('merge mode skips exercises whose name already exists for the user', async () => {
    const u = await makeUser({ email: 'a@x' });
    await prisma.exercise.create({
      data: {
        userId: u.id,
        name: 'Imported Bench',
        type: 'barbell',
        muscleGroups: '["chest"]',
        isCustom: true, // user's own custom version
      },
    });
    getCurrentUserMock.mockResolvedValue(u);
    const res = await importPost(jsonReq({ payload: sampleExport() }));
    const body = await res.json();
    expect(body.exercisesCreated).toBe(1); // only Squat
    expect(body.exercisesSkipped).toBe(1); // Bench already existed

    // The user's custom row was NOT overwritten.
    const bench = await prisma.exercise.findUnique({
      where: { userId_name: { userId: u.id, name: 'Imported Bench' } },
    });
    expect(bench?.isCustom).toBe(true);

    // Workouts were still created and bound to the user's existing
    // (custom) Bench, not the imported one.
    const sets = await prisma.setLog.findMany({
      where: { exerciseId: bench!.id },
    });
    expect(sets.length).toBeGreaterThan(0);
  });

  it('replace mode requires explicit confirmation', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);
    const res = await importPost(
      jsonReq({ payload: sampleExport(), mode: 'replace' }),
    );
    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.error).toMatch(/REPLACE/);
  });

  it('replace mode wipes existing user-owned data first', async () => {
    const u = await makeUser({ email: 'a@x' });
    const existing = await prisma.exercise.create({
      data: {
        userId: u.id,
        name: 'Old Custom',
        type: 'barbell',
        muscleGroups: '[]',
      },
    });
    await prisma.workout.create({
      data: {
        userId: u.id,
        date: new Date('2026-01-01'),
        setLogs: { create: [{ exerciseId: existing.id, setNumber: 1, reps: 1 }] },
      },
    });
    expect(await prisma.workout.count({ where: { userId: u.id } })).toBe(1);
    expect(await prisma.exercise.count({ where: { userId: u.id } })).toBe(1);

    getCurrentUserMock.mockResolvedValue(u);
    const res = await importPost(
      jsonReq({
        payload: sampleExport(),
        mode: 'replace',
        confirm: 'REPLACE',
      }),
    );
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.mode).toBe('replace');

    // Old custom is gone; new imported data is in place.
    expect(
      await prisma.exercise.findUnique({
        where: { userId_name: { userId: u.id, name: 'Old Custom' } },
      }),
    ).toBeNull();
    expect(await prisma.exercise.count({ where: { userId: u.id } })).toBe(2);
    expect(await prisma.workout.count({ where: { userId: u.id } })).toBe(2);
  });

  it('keeps imports scoped to the actor, never to other users', async () => {
    const me = await makeUser({ email: 'me@x' });
    const other = await makeUser({ email: 'other@x' });
    await prisma.exercise.create({
      data: {
        userId: other.id,
        name: 'Other User Exercise',
        type: 'barbell',
        muscleGroups: '[]',
      },
    });
    getCurrentUserMock.mockResolvedValue(me);
    await importPost(jsonReq({ payload: sampleExport() }));

    // The other user's data is completely untouched.
    expect(await prisma.exercise.count({ where: { userId: other.id } })).toBe(1);
    expect(await prisma.workout.count({ where: { userId: other.id } })).toBe(0);
    expect(
      (await prisma.exercise.findFirst({ where: { userId: other.id } }))?.name,
    ).toBe('Other User Exercise');
  });
});
