import { describe, it, expect } from 'vitest';
// `applyFireModeChange` will be introduced by Task 4.3 as a pure helper in
// `form-state.ts` so that the ExchangeMatchForm can reuse it on mode-toggle
// and guarantee state hygiene (no stale COUNT_IN_WINDOW fields leaking into a
// PER_EXCHANGE rule payload and vice-versa). These tests pin the contract.
import { applyFireModeChange, initialForm, toRequest, validateStep } from './form-state';

describe('initialForm', () => {
  it('defaults to env-wide ROUTE_METRIC with safe intervals', () => {
    const f = initialForm();
    expect(f.scopeKind).toBe('env');
    expect(f.conditionKind).toBe('ROUTE_METRIC');
    expect(f.evaluationIntervalSeconds).toBeGreaterThanOrEqual(5);
    expect(f.enabled).toBe(true);
  });
});

describe('toRequest', () => {
  it('strips empty scope fields for env-wide rules', () => {
    const f = initialForm();
    f.name = 'test';
    const req = toRequest(f);
    const scope = (req.condition as unknown as { scope: Record<string, string | undefined> }).scope;
    expect(scope.appSlug).toBeUndefined();
    expect(scope.routeId).toBeUndefined();
    expect(scope.agentId).toBeUndefined();
  });

  it('includes appSlug for app/route/agent scopes', () => {
    const f = initialForm();
    f.scopeKind = 'app';
    f.appSlug = 'orders';
    const req = toRequest(f);
    const scope = (req.condition as unknown as { scope: Record<string, string | undefined> }).scope;
    expect(scope.appSlug).toBe('orders');
  });
});

describe('validateStep', () => {
  it('flags blank name on scope step', () => {
    expect(validateStep('scope', initialForm())).toContain('Name is required.');
  });
  it('flags app requirement for app-scope', () => {
    const f = initialForm();
    f.name = 'x';
    f.scopeKind = 'app';
    expect(validateStep('scope', f).some((e) => /App is required/.test(e))).toBe(true);
  });
  it('flags intervals below floor on trigger step', () => {
    const f = initialForm();
    f.evaluationIntervalSeconds = 1;
    expect(validateStep('trigger', f).some((e) => /Evaluation interval/.test(e))).toBe(true);
  });

  it('flags missing fireMode on EXCHANGE_MATCH condition step', () => {
    const f = initialForm();
    f.conditionKind = 'EXCHANGE_MATCH';
    // Simulate the old bug: condition payload without a fireMode.
    f.condition = { kind: 'EXCHANGE_MATCH', scope: {} } as typeof f.condition;
    expect(validateStep('condition', f).some((e) => /Fire mode is required/.test(e))).toBe(true);
  });

  it('flags missing threshold/window on COUNT_IN_WINDOW', () => {
    const f = initialForm();
    f.conditionKind = 'EXCHANGE_MATCH';
    f.condition = {
      kind: 'EXCHANGE_MATCH',
      scope: {},
      fireMode: 'COUNT_IN_WINDOW',
    } as unknown as typeof f.condition;
    const errs = validateStep('condition', f);
    expect(errs.some((e) => /Threshold is required/.test(e))).toBe(true);
    expect(errs.some((e) => /Window/.test(e))).toBe(true);
  });

  it('passes when EXCHANGE_MATCH PER_EXCHANGE has a fire mode', () => {
    const f = initialForm();
    f.conditionKind = 'EXCHANGE_MATCH';
    f.condition = {
      kind: 'EXCHANGE_MATCH',
      scope: {},
      fireMode: 'PER_EXCHANGE',
    } as unknown as typeof f.condition;
    expect(validateStep('condition', f)).toEqual([]);
  });
});

// ----------------------------------------------------------------------------
// Task 4.2 RED: fire-mode toggle state hygiene
//
// `applyFireModeChange(form, newMode)` is the pure helper (Task 4.3) that the
// ExchangeMatchForm's Fire-mode <Select> will call in place of the current
// raw `patch({ fireMode })`. It must guarantee:
//
//   - Switching to PER_EXCHANGE clears COUNT_IN_WINDOW-only fields
//     (condition.threshold, condition.windowSeconds) AND forces
//     top-level reNotifyMinutes = 0, forDurationSeconds = 0. PER_EXCHANGE
//     is exactly-once-per-exchange — re-notify cadence and hold-duration
//     are semantically meaningless and must not leak into toRequest().
//   - Switching back to COUNT_IN_WINDOW resets to defaults (zero), never
//     restoring stale values from the previous COUNT_IN_WINDOW session.
//
// These tests will fail until Task 4.3 introduces the helper.
// ----------------------------------------------------------------------------
describe('applyFireModeChange (fire-mode toggle hygiene)', () => {
  const exchangeMatchForm = () => {
    const f = initialForm();
    f.conditionKind = 'EXCHANGE_MATCH';
    f.condition = {
      kind: 'EXCHANGE_MATCH',
      scope: {},
      fireMode: 'COUNT_IN_WINDOW',
    } as unknown as typeof f.condition;
    return f;
  };

  it('clears COUNT_IN_WINDOW fields when switching to PER_EXCHANGE', () => {
    let f = exchangeMatchForm();
    // Simulate a user who filled in COUNT_IN_WINDOW fields first.
    f.condition = {
      ...(f.condition as Record<string, unknown>),
      threshold: 5,
      windowSeconds: 300,
    } as typeof f.condition;

    f = applyFireModeChange(f, 'PER_EXCHANGE');

    const c = f.condition as Record<string, unknown>;
    expect(c.fireMode).toBe('PER_EXCHANGE');
    expect(c.threshold ?? 0).toBe(0);
    expect(c.windowSeconds ?? 0).toBe(0);
  });

  it('resets to defaults (not stale values) when switching back to COUNT_IN_WINDOW', () => {
    let f = exchangeMatchForm();
    f.condition = {
      ...(f.condition as Record<string, unknown>),
      threshold: 5,
      windowSeconds: 300,
    } as typeof f.condition;

    f = applyFireModeChange(f, 'PER_EXCHANGE');
    f = applyFireModeChange(f, 'COUNT_IN_WINDOW');

    const c = f.condition as Record<string, unknown>;
    expect(c.fireMode).toBe('COUNT_IN_WINDOW');
    // Must be fresh defaults, not the stale 5 / 300 the user typed before.
    expect(c.threshold ?? 0).toBe(0);
    expect(c.windowSeconds ?? 0).toBe(0);
  });

  it('forces reNotifyMinutes=0 and forDurationSeconds=0 when switching to PER_EXCHANGE', () => {
    let f = exchangeMatchForm();
    f.reNotifyMinutes = 60;
    f.forDurationSeconds = 120;

    f = applyFireModeChange(f, 'PER_EXCHANGE');

    expect(f.reNotifyMinutes).toBe(0);
    expect(f.forDurationSeconds).toBe(0);
  });
});
