import { describe, it, expect } from 'vitest';
import { parseAIWorkout } from '@/lib/ai/workoutSchema';

describe('parseAIWorkout', () => {
  const valid = {
    name: 'Upper — Shoulders',
    notes: 'Overhead press focus',
    exercises: [
      {
        exerciseId: 'cabc',
        exerciseName: 'Overhead Press',
        order: 0,
        sets: 4,
        reps: 6,
        suggestedWeight: 95,
        suggestedWeightUnit: 'lbs',
        rpe: 8,
      },
      {
        exerciseId: 'cdef',
        exerciseName: 'Assault Bike',
        order: 1,
        sets: 1,
        durationSeconds: 600,
        gear: 3,
      },
    ],
  };

  it('accepts a valid single workout', () => {
    const r = parseAIWorkout(JSON.stringify(valid));
    expect(r.ok).toBe(true);
    if (r.ok) {
      expect(r.workout.name).toBe('Upper — Shoulders');
      expect(r.workout.exercises).toHaveLength(2);
      expect(r.workout.exercises[0].suggestedWeight).toBe(95);
      expect(r.workout.exercises[1].gear).toBe(3);
    }
  });

  it('rounds decimal ints from the model instead of failing (RPE 7.5 -> 8)', () => {
    // Local models (Qwen via SparkControl) sometimes emit half-step RPE or
    // other decimals where we expect integers. These must round, not blow up
    // the whole parse (the bug that surfaced on the first SparkControl run).
    const variant = structuredClone(valid);
    const ex = variant.exercises[0] as Record<string, unknown>;
    ex.rpe = 7.5; // -> 8
    ex.reps = 8.6; // -> 9
    ex.sets = 3.4; // -> 3
    const r = parseAIWorkout(JSON.stringify(variant));
    expect(r.ok).toBe(true);
    if (r.ok) {
      expect(r.workout.exercises[0].rpe).toBe(8);
      expect(r.workout.exercises[0].reps).toBe(9);
      expect(r.workout.exercises[0].sets).toBe(3);
    }
  });

  it('accepts null exerciseId for unresolved exercises', () => {
    const variant = structuredClone(valid);
    variant.exercises[0].exerciseId = null as unknown as string;
    const r = parseAIWorkout(JSON.stringify(variant));
    expect(r.ok).toBe(true);
  });

  it('strips markdown fences and commentary', () => {
    const wrapped =
      "Here's today's session:\n\n```json\n" +
      JSON.stringify(valid) +
      '\n```\n\nEnjoy!';
    const r = parseAIWorkout(wrapped);
    expect(r.ok).toBe(true);
  });

  it('rejects when no JSON is present', () => {
    const r = parseAIWorkout('the model just said hi');
    expect(r.ok).toBe(false);
    if (!r.ok) expect(r.reason).toMatch(/Could not find/);
  });

  it('rejects a parse-level syntax error inside balanced braces', () => {
    const r = parseAIWorkout('{ "name": "x", }');
    expect(r.ok).toBe(false);
    if (!r.ok) expect(r.reason).toMatch(/parse error/i);
  });

  it('rejects when the shape is wrong (missing exercises)', () => {
    const r = parseAIWorkout(JSON.stringify({ name: 'X' }));
    expect(r.ok).toBe(false);
    if (!r.ok) expect(r.reason).toMatch(/shape/);
  });

  it('rejects an out-of-range gear', () => {
    const variant = structuredClone(valid);
    variant.exercises[1].gear = 9; // gear is 1-5
    const r = parseAIWorkout(JSON.stringify(variant));
    expect(r.ok).toBe(false);
  });

  it('rejects an empty exercise name', () => {
    const variant = structuredClone(valid);
    variant.exercises[0].exerciseName = '';
    const r = parseAIWorkout(JSON.stringify(variant));
    expect(r.ok).toBe(false);
  });
});
