import { describe, expect, it } from 'vitest';
import { compileRelative, parseRelative } from './dateMacro';
import type { Rule } from './types';

const r = (o: Partial<Rule>): Rule => ({ field: 'release_date', operator: 'inLast', value: '7', unit: 'day', ...o });

describe('compileRelative', () => {
  it('maps release_date/inLast → released_inthelast', () => {
    expect(compileRelative(r({}))).toBe('released_inthelast:"7 day"');
  });
  it('maps release_date/notInLast → released_notinthelast', () => {
    expect(compileRelative(r({ operator: 'notInLast', value: '14', unit: 'month' }))).toBe('released_notinthelast:"14 month"');
  });
  it('maps added_date/inLast → added_inthelast', () => {
    expect(compileRelative(r({ field: 'added_date', value: '2', unit: 'year' }))).toBe('added_inthelast:"2 year"');
  });
  it('maps added_date/notInLast → added_notinthelast', () => {
    expect(compileRelative(r({ field: 'added_date', operator: 'notInLast', value: '3', unit: 'week' }))).toBe('added_notinthelast:"3 week"');
  });
  it('returns null for a non-relative rule', () => {
    expect(compileRelative(r({ operator: 'before' }))).toBeNull();
  });
  it('returns null when N is invalid', () => {
    expect(compileRelative(r({ value: '0' }))).toBeNull();
  });
  it('returns null when N is not an integer', () => {
    expect(compileRelative(r({ value: 'abc' }))).toBeNull();
  });
  it('returns null when N is empty', () => {
    expect(compileRelative(r({ value: '' }))).toBeNull();
  });
  it('returns null when N is decimal notation', () => {
    expect(compileRelative(r({ value: '7.0' }))).toBeNull();
  });
  it('returns null when N is scientific notation', () => {
    expect(compileRelative(r({ value: '1e2' }))).toBeNull();
  });
  it('returns null when unit is undefined', () => {
    expect(compileRelative(r({ unit: undefined }))).toBeNull();
  });
});

describe('parseRelative', () => {
  it('round-trips released_inthelast', () => {
    expect(parseRelative('released_inthelast', '7 day')).toEqual({ field: 'release_date', operator: 'inLast', value: '7', unit: 'day' });
  });
  it('round-trips released_notinthelast', () => {
    expect(parseRelative('released_notinthelast', '14 month')).toEqual({ field: 'release_date', operator: 'notInLast', value: '14', unit: 'month' });
  });
  it('round-trips added_inthelast', () => {
    expect(parseRelative('added_inthelast', '2 year')).toEqual({ field: 'added_date', operator: 'inLast', value: '2', unit: 'year' });
  });
  it('round-trips added_notinthelast', () => {
    expect(parseRelative('added_notinthelast', '3 week')).toEqual({ field: 'added_date', operator: 'notInLast', value: '3', unit: 'week' });
  });
  it('returns null for an unrelated field', () => {
    expect(parseRelative('title', '7 day')).toBeNull();
  });
  it('returns null for a malformed value', () => {
    expect(parseRelative('released_inthelast', 'soon')).toBeNull();
  });
});

describe('composed round-trip', () => {
  it('compiles and parses back release_date/inLast', () => {
    const rule = r({});
    const out = compileRelative(rule)!;
    const [, field, quoted] = /^(\S+):"(.+)"$/.exec(out)!;
    expect(parseRelative(field, quoted)).toEqual(rule);
  });
  it('compiles and parses back release_date/notInLast', () => {
    const rule = r({ operator: 'notInLast', value: '14', unit: 'month' });
    const out = compileRelative(rule)!;
    const [, field, quoted] = /^(\S+):"(.+)"$/.exec(out)!;
    expect(parseRelative(field, quoted)).toEqual(rule);
  });
  it('compiles and parses back added_date/inLast', () => {
    const rule = r({ field: 'added_date', value: '2', unit: 'year' });
    const out = compileRelative(rule)!;
    const [, field, quoted] = /^(\S+):"(.+)"$/.exec(out)!;
    expect(parseRelative(field, quoted)).toEqual(rule);
  });
  it('compiles and parses back added_date/notInLast', () => {
    const rule = r({ field: 'added_date', operator: 'notInLast', value: '3', unit: 'week' });
    const out = compileRelative(rule)!;
    const [, field, quoted] = /^(\S+):"(.+)"$/.exec(out)!;
    expect(parseRelative(field, quoted)).toEqual(rule);
  });
});
