import { describe, it, expect } from 'vitest';
import { ok, err } from '@shared/kernel/fp';
import {
  Answers,
  initial,
  STEPS,
  lageUren,
  currentStep,
  next,
  back,
  gaNaarStap,
  submit,
  resolve,
  reduce,
  IntakeState,
} from './intake.machine';

const answering = (answers: Answers, cursor = 0, scholingThreshold = 1000): IntakeState => ({
  tag: 'Answering',
  answers,
  cursor,
  errors: {},
  scholingThreshold,
});

describe('STEPS (fixed) and inline questions', () => {
  it('always has the same three steps', () => {
    expect(STEPS).toEqual(['buitenland', 'werk', 'review']);
  });

  it('reveals the buitenland detail questions inline only when worked abroad', () => {
    // No new step; instead these fields become required within the buitenland step.
    expect(next(answering({ buitenlandGewerkt: 'ja' })).tag).toBe('Answering'); // land/uren missing -> blocked
    expect((next(answering({ buitenlandGewerkt: 'ja' })) as any).errors.land).toBeTruthy();
    expect(next(answering({ buitenlandGewerkt: 'nee' })).tag).toBe('Answering'); // valid, advances (cursor moves)
    expect((next(answering({ buitenlandGewerkt: 'nee' })) as any).cursor).toBe(1);
  });

  it('reveals the scholing question only when NL-hours are below the threshold', () => {
    expect(lageUren({ uren: '500' })).toBe(true);
    expect(lageUren({ uren: '4160' })).toBe(false);
  });

  it('uses the (server-owned) threshold passed in, not a hardcoded constant', () => {
    // Same hours, different threshold → different visibility. Proves de-hardcoding.
    expect(lageUren({ uren: '1500' }, 1000)).toBe(false);
    expect(lageUren({ uren: '1500' }, 2000)).toBe(true);
    // And the threshold from state flows through submit:
    const lowThreshold = submit(
      answering({ buitenlandGewerkt: 'nee', uren: '1500', punten: '200' }, 0, 2000),
    );
    expect(lowThreshold.tag).toBe('Answering'); // scholing now required (1500 < 2000), unanswered → blocked
    expect((lowThreshold as any).errors.scholingGevolgd).toBeTruthy();
  });
});

describe('navigation', () => {
  it('Next is a no-op (sets an error) when the current step is invalid', () => {
    const s = next(initial); // buitenland unanswered
    expect(s.tag).toBe('Answering');
    expect((s as any).cursor).toBe(0);
    expect((s as any).errors.buitenlandGewerkt).toBeTruthy();
  });

  it('Next advances once the step is valid', () => {
    const s = next(answering({ buitenlandGewerkt: 'nee' }));
    expect((s as any).cursor).toBe(1);
    expect(currentStep(s as any)).toBe('werk');
  });

  it('editing an answer leaves the cursor fixed (steps never collapse)', () => {
    const edited = reduce(answering({ buitenlandGewerkt: 'ja' }, 1), {
      tag: 'SetAnswer',
      key: 'buitenlandGewerkt',
      value: 'nee',
    });
    expect((edited as any).cursor).toBe(1); // cursor untouched; only inline questions change
  });

  it('Back never goes below the first step', () => {
    expect(back(initial)).toBe(initial);
  });

  it('gaNaarStap jumps back to an earlier step, clearing errors', () => {
    const s = answering({ buitenlandGewerkt: 'nee' }, 2);
    expect((gaNaarStap(s, 0) as any).cursor).toBe(0);
  });

  it('gaNaarStap ignores a same/forward jump and jumps outside Answering', () => {
    const s = answering({ buitenlandGewerkt: 'nee' }, 1);
    expect(gaNaarStap(s, 1)).toBe(s); // same step -> no-op
    expect(gaNaarStap(s, 2)).toBe(s); // forward -> no-op
    const submitting = submit(answering({ buitenlandGewerkt: 'nee', uren: '4160' }, 2));
    expect(gaNaarStap(submitting, 0)).toBe(submitting); // not Answering -> no-op
  });
});

describe('submit', () => {
  // High hours: no scholing question, so no punten is asked or collected.
  const complete: Answers = { buitenlandGewerkt: 'nee', uren: '4160' };

  it('reaches Submitting ONLY with valid answers', () => {
    // Bad punten only blocks when scholing was followed (otherwise punten is ignored).
    expect(
      submit(
        answering({ buitenlandGewerkt: 'nee', uren: '500', scholingGevolgd: 'ja', punten: 'x' }),
      ).tag,
    ).toBe('Answering');
    const good = submit(answering(complete));
    expect(good.tag).toBe('Submitting');
    expect((good as any).data.uren).toBe(4160);
    expect((good as any).data.punten).toBeUndefined(); // not collected without scholing
  });

  it('punten is required only when aanvullende scholing was gevolgd', () => {
    // scholing = ja but punten missing -> blocked on punten.
    const missing = submit(
      answering({ buitenlandGewerkt: 'nee', uren: '500', scholingGevolgd: 'ja' }),
    );
    expect(missing.tag).toBe('Answering');
    expect((missing as any).errors.punten).toBeTruthy();
    // scholing = nee -> punten not required, submits without it.
    expect(
      submit(answering({ buitenlandGewerkt: 'nee', uren: '500', scholingGevolgd: 'nee' })).tag,
    ).toBe('Submitting');
  });

  it('low hours requires the scholing answer before submit', () => {
    const noScholing = submit(answering({ buitenlandGewerkt: 'nee', uren: '500' }));
    expect(noScholing.tag).toBe('Answering'); // scholing question is required, unanswered
    const withScholing = submit(
      answering({ buitenlandGewerkt: 'nee', uren: '500', scholingGevolgd: 'ja', punten: '200' }),
    );
    expect(withScholing.tag).toBe('Submitting');
    expect((withScholing as any).data.aanvullendeScholing).toBe(true);
    expect((withScholing as any).data.punten).toBe(200);
  });

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

describe('reduce (message-driven happy path)', () => {
  it('drives abroad branch end to end', () => {
    let s: IntakeState = initial;
    // Step 1: buitenland — the country/hours questions reveal inline (same step).
    s = reduce(s, { tag: 'SetAnswer', key: 'buitenlandGewerkt', value: 'ja' });
    s = reduce(s, { tag: 'SetAnswer', key: 'land', value: 'België' });
    s = reduce(s, { tag: 'SetAnswer', key: 'buitenlandseUren', value: '800' });
    s = reduce(s, { tag: 'Next' });
    expect(currentStep(s as any)).toBe('werk');
    // Step 2: werk — uren + punten (no inline scholing, hours are high).
    s = reduce(s, { tag: 'SetAnswer', key: 'uren', value: '4160' });
    s = reduce(s, { tag: 'SetAnswer', key: 'punten', value: '200' });
    s = reduce(s, { tag: 'Next' });
    expect(currentStep(s as any)).toBe('review');
    s = reduce(s, { tag: 'Submit' });
    expect(s.tag).toBe('Submitting');
    s = reduce(s, { tag: 'SubmitConfirmed' });
    expect(s.tag).toBe('Submitted');
  });
});
