import { describe, it, expect } from 'vitest';
import { ok, err } from '@shared/kernel/fp';
import { initialUpload } from '@shared/upload/upload.machine';
import { initial, next, back, submit, resolve, reduce, WizardState } from './herregistratie.machine';

const editing1 = (uren: string, jaren = '5', punten = ''): WizardState => ({ tag: 'Editing', step: 1, draft: { uren, jaren, punten }, errors: {}, upload: initialUpload });
const editing2 = (uren: string, punten: string, jaren = '5'): WizardState => ({ tag: 'Editing', step: 2, draft: { uren, jaren, punten }, errors: {}, upload: initialUpload });
const editing3 = (uren: string, punten: string, jaren = '5'): WizardState => ({ tag: 'Editing', step: 3, draft: { uren, jaren, punten }, errors: {}, upload: initialUpload });

describe('wizard.machine', () => {
  it('next advances only when step 1 parses', () => {
    expect(next(initial).tag).toBe('Editing'); // empty uren -> stays, with error
    expect((next(initial) as any).errors.uren).toBeTruthy();
    expect((next(editing1('4160')) as any).step).toBe(2);
  });

  it('next advances step 2 → 3 only when punten parses', () => {
    expect((next(editing2('4160', 'x')) as any).step).toBe(2); // invalid punten -> stays
    expect((next(editing2('4160', 'x')) as any).errors.punten).toBeTruthy();
    expect((next(editing2('4160', '200')) as any).step).toBe(3);
  });

  it('submit reaches Submitting ONLY from step 3 with fully valid data', () => {
    expect(submit(editing2('4160', '200')).tag).toBe('Editing'); // not on step 3 -> no Submitting
    expect(submit(editing3('4160', 'x')).tag).toBe('Editing'); // invalid punten
    const good = submit(editing3('4160', '200'));
    expect(good.tag).toBe('Submitting');
    expect((good as any).data).toEqual({ uren: 4160, jaren: 5, punten: 200, documents: [] });
  });

  it('next requires BOTH step-1 fields (uren and jaren)', () => {
    expect((next(editing1('4160', '')) as any).errors.jaren).toBeTruthy(); // jaren empty -> stays
    expect((next(editing1('4160', '')) as any).step).toBe(1);
    expect((next(editing1('4160', '5')) as any).step).toBe(2); // both valid -> advance
  });

  it('back steps down one (3 → 2 → 1) and is a no-op from step 1', () => {
    expect(back(initial)).toBe(initial); // step 1, nothing to go back to
    expect((back(editing3('1', '2')) as any).step).toBe(2);
    expect((back(editing2('1', '2')) as any).step).toBe(1);
    expect(resolve(initial, ok(undefined))).toBe(initial); // not Submitting
  });

  it('resolve maps Submitting to Submitted / Failed', () => {
    const submitting = submit(editing3('4160', '200'));
    expect(resolve(submitting, ok(undefined)).tag).toBe('Submitted');
    expect(resolve(submitting, err('boom')).tag).toBe('Failed');
  });
});

describe('reduce (message-driven)', () => {
  it('drives the full happy path via messages', () => {
    let s: WizardState = initial;
    s = reduce(s, { tag: 'SetField', key: 'uren', value: '4160' });
    s = reduce(s, { tag: 'SetField', key: 'jaren', value: '5' });
    s = reduce(s, { tag: 'Next' });
    expect(s.tag === 'Editing' && s.step).toBe(2);
    s = reduce(s, { tag: 'SetField', key: 'punten', value: '200' });
    s = reduce(s, { tag: 'Next' });
    expect(s.tag === 'Editing' && s.step).toBe(3);
    s = reduce(s, { tag: 'Submit' });
    expect(s.tag).toBe('Submitting');
    s = reduce(s, { tag: 'SubmitConfirmed' });
    expect(s.tag).toBe('Submitted');
  });

  it('blocks submit until required documents are satisfied', () => {
    const cat = { categoryId: 'bewijs', label: 'Bewijs', description: '', required: true, acceptedTypes: [], maxSizeMb: 10, multiple: false, allowPostDelivery: true };
    let s = reduce(editing3('4160', '200'), { tag: 'Upload', msg: { type: 'CategoriesLoaded', categories: [cat] } });
    s = reduce(s, { tag: 'Submit' });
    expect(s.tag).toBe('Editing');
    expect((s as any).errors.documenten).toBeTruthy();
    s = reduce(s, { tag: 'Upload', msg: { type: 'DeliveryChannelChanged', categoryId: 'bewijs', channel: 'post' } });
    s = reduce(s, { tag: 'Submit' });
    expect(s.tag).toBe('Submitting');
    expect((s as any).data.documents).toEqual([{ categoryId: 'bewijs', channel: 'post' }]);
  });

  it('SubmitFailed then Retry returns to Submitting with the same data', () => {
    let s = reduce(reduce(editing3('4160', '200'), { tag: 'Submit' }), { tag: 'SubmitFailed', error: 'boom' });
    expect(s.tag).toBe('Failed');
    s = reduce(s, { tag: 'Retry' });
    expect(s.tag).toBe('Submitting');
    expect((s as any).data).toEqual({ uren: 4160, jaren: 5, punten: 200, documents: [] });
  });

  it('Seed mounts an arbitrary state', () => {
    expect(reduce(initial, { tag: 'Seed', state: editing2('1', '2') }).tag).toBe('Editing');
  });
});
