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 { prisma } from '@/lib/prisma';
import { POST as saveImport } from '@/app/api/workouts/import/save/route';

function jsonReq(body: unknown): Request {
  return new Request('http://x/api/workouts/import/save', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(body),
  });
}

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();
});

describe('POST /api/workouts/import/save', () => {
  it('returns 401 when unauthenticated', async () => {
    getCurrentUserMock.mockResolvedValue(null);
    const res = await saveImport(
      jsonReq({ workouts: [{ date: '2026-01-01', exercises: [] }] }),
    );
    expect(res.status).toBe(401);
  });

  it('returns 400 on empty workouts array (Zod min(1))', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);
    const res = await saveImport(jsonReq({ workouts: [] }));
    expect(res.status).toBe(400);
  });

  it('matches existing exercise by case-insensitive name', async () => {
    const u = await makeUser({ email: 'a@x' });
    const bench = await prisma.exercise.create({
      data: {
        userId: u.id,
        name: 'Bench Press',
        type: 'barbell',
        muscleGroups: '[]',
      },
    });
    getCurrentUserMock.mockResolvedValue(u);

    const res = await saveImport(
      jsonReq({
        workouts: [
          {
            date: '2026-01-15',
            exercises: [
              {
                name: 'bench press', // lowercased — should still match
                sets: [{ reps: 5, weight: 225, weightUnit: 'lbs' }],
              },
            ],
          },
        ],
      }),
    );
    expect(res.status).toBe(200);

    // Should NOT create a duplicate exercise.
    expect(await prisma.exercise.count({ where: { userId: u.id } })).toBe(1);

    const sets = await prisma.setLog.findMany({
      where: { exerciseId: bench.id },
    });
    expect(sets).toHaveLength(1);
    expect(sets[0].weight).toBe(225);
  });

  it('creates new exercise (isCustom=true, type defaults to "other") when no match', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);

    const res = await saveImport(
      jsonReq({
        workouts: [
          {
            date: '2026-01-15',
            exercises: [
              {
                name: 'Brand New Exercise',
                sets: [{ reps: 10 }],
              },
            ],
          },
        ],
      }),
    );
    expect(res.status).toBe(200);

    const ex = await prisma.exercise.findUnique({
      where: {
        userId_name: { userId: u.id, name: 'Brand New Exercise' },
      },
    });
    expect(ex).toBeTruthy();
    expect(ex?.isCustom).toBe(true);
    expect(ex?.type).toBe('other');
  });

  it('honors explicit existingExerciseId over name lookup', async () => {
    const u = await makeUser({ email: 'a@x' });
    const a = await prisma.exercise.create({
      data: {
        userId: u.id,
        name: 'Squat',
        type: 'barbell',
        muscleGroups: '[]',
      },
    });
    const b = await prisma.exercise.create({
      data: {
        userId: u.id,
        name: 'Different Squat Variation',
        type: 'barbell',
        muscleGroups: '[]',
      },
    });
    getCurrentUserMock.mockResolvedValue(u);

    // Even though the name matches `a`, existingExerciseId says use `b`.
    await saveImport(
      jsonReq({
        workouts: [
          {
            date: '2026-01-15',
            exercises: [
              {
                name: 'Squat',
                existingExerciseId: b.id,
                sets: [{ reps: 5, weight: 315 }],
              },
            ],
          },
        ],
      }),
    );

    const sets = await prisma.setLog.findMany({
      where: { exerciseId: b.id },
    });
    expect(sets).toHaveLength(1);
    expect(
      await prisma.setLog.count({ where: { exerciseId: a.id } }),
    ).toBe(0);
  });

  it('creates multiple workouts in one call and returns the ids', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);

    const res = await saveImport(
      jsonReq({
        workouts: [
          {
            date: '2026-01-01',
            exercises: [{ name: 'Squat', sets: [{ reps: 5 }] }],
          },
          {
            date: '2026-01-03',
            exercises: [{ name: 'Bench', sets: [{ reps: 5 }] }],
          },
          {
            date: '2026-01-05',
            exercises: [{ name: 'Deadlift', sets: [{ reps: 5 }] }],
          },
        ],
      }),
    );
    const body = await res.json();
    expect(body.count).toBe(3);
    expect(body.created).toHaveLength(3);

    expect(await prisma.workout.count({ where: { userId: u.id } })).toBe(3);
  });

  it('numbers sets sequentially per exercise per workout', async () => {
    const u = await makeUser({ email: 'a@x' });
    getCurrentUserMock.mockResolvedValue(u);

    await saveImport(
      jsonReq({
        workouts: [
          {
            date: '2026-01-15',
            exercises: [
              {
                name: 'Squat',
                sets: [
                  { reps: 5, weight: 135 },
                  { reps: 5, weight: 185 },
                  { reps: 5, weight: 225 },
                ],
              },
            ],
          },
        ],
      }),
    );

    const sets = await prisma.setLog.findMany({
      orderBy: { setNumber: 'asc' },
    });
    expect(sets.map((s) => s.setNumber)).toEqual([1, 2, 3]);
    expect(sets.map((s) => s.weight)).toEqual([135, 185, 225]);
  });
});
