import { describe, it, expect } from 'vitest';
import { parse } from '../../push3-transpiler/src/parser';
import { isValid } from '../mutate';
import {
  generateSeedVariant,
  selectVariants,
  enumerateVariants,
  getTaxThresholds,
  STAKED_THRESHOLDS,
  PENALTY_THRESHOLDS,
  BULL_VARIANTS,
  BEAR_VARIANTS,
  TAX_DISTRIBUTIONS,
  type SeedVariantParams,
  type TaxDistribution,
} from '../seed-generator';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function parseAndValidate(src: string): boolean {
  try {
    const program = parse(src);
    return isValid(program);
  } catch {
    return false;
  }
}

const DEFAULT_PARAMS: SeedVariantParams = {
  stakedThreshold: 91,
  penaltyThreshold: 50,
  bull: BULL_VARIANTS[0]!,
  bear: BEAR_VARIANTS[0]!,
  taxDistribution: 'exponential',
};

// ---------------------------------------------------------------------------
// getTaxThresholds
// ---------------------------------------------------------------------------

describe('getTaxThresholds', () => {
  for (const dist of TAX_DISTRIBUTIONS) {
    it(`returns 29 values for ${dist}`, () => {
      const t = getTaxThresholds(dist as TaxDistribution);
      expect(t).toHaveLength(29);
    });

    it(`all ${dist} thresholds are positive`, () => {
      const t = getTaxThresholds(dist as TaxDistribution);
      for (const v of t) {
        expect(v).toBeGreaterThan(0n);
      }
    });

    it(`${dist} thresholds are strictly increasing`, () => {
      const t = getTaxThresholds(dist as TaxDistribution);
      for (let i = 1; i < t.length; i++) {
        expect(t[i]).toBeGreaterThan(t[i - 1]!);
      }
    });

    it(`${dist} thresholds are below 1e18`, () => {
      const t = getTaxThresholds(dist as TaxDistribution);
      const ONE_E18 = 1_000_000_000_000_000_000n;
      for (const v of t) {
        expect(v).toBeLessThan(ONE_E18);
      }
    });
  }

  it('linear thresholds are evenly spaced', () => {
    const t = getTaxThresholds('linear');
    const ONE_E18 = 1_000_000_000_000_000_000n;
    // Each gap should be approximately 1e18/30
    const expected = ONE_E18 / 30n;
    for (let i = 1; i < t.length; i++) {
      const gap = t[i]! - t[i - 1]!;
      // Allow ±1 for integer rounding
      expect(gap - expected).toBeGreaterThanOrEqual(-1n);
      expect(gap - expected).toBeLessThanOrEqual(1n);
    }
  });
});

// ---------------------------------------------------------------------------
// generateSeedVariant — structure and validity
// ---------------------------------------------------------------------------

describe('generateSeedVariant', () => {
  it('generates parseable Push3 with default seed params', () => {
    const src = generateSeedVariant(DEFAULT_PARAMS);
    expect(() => parse(src)).not.toThrow();
  });

  it('generates a valid program with default seed params', () => {
    const src = generateSeedVariant(DEFAULT_PARAMS);
    expect(parseAndValidate(src)).toBe(true);
  });

  it('generates valid programs for all staked thresholds', () => {
    for (const st of STAKED_THRESHOLDS) {
      const src = generateSeedVariant({ ...DEFAULT_PARAMS, stakedThreshold: st });
      expect(parseAndValidate(src)).toBe(true);
    }
  });

  it('generates valid programs for all penalty thresholds', () => {
    for (const pt of PENALTY_THRESHOLDS) {
      const src = generateSeedVariant({ ...DEFAULT_PARAMS, penaltyThreshold: pt });
      expect(parseAndValidate(src)).toBe(true);
    }
  });

  it('generates valid programs for all bull variants', () => {
    for (const bull of BULL_VARIANTS) {
      const src = generateSeedVariant({ ...DEFAULT_PARAMS, bull });
      expect(parseAndValidate(src)).toBe(true);
    }
  });

  it('generates valid programs for all bear variants', () => {
    for (const bear of BEAR_VARIANTS) {
      const src = generateSeedVariant({ ...DEFAULT_PARAMS, bear });
      expect(parseAndValidate(src)).toBe(true);
    }
  });

  it('generates valid programs for all tax distributions', () => {
    for (const taxDistribution of TAX_DISTRIBUTIONS) {
      const src = generateSeedVariant({ ...DEFAULT_PARAMS, taxDistribution });
      expect(parseAndValidate(src)).toBe(true);
    }
  });

  it('default params reproduce optimizer_seed.push3 constants', () => {
    const src = generateSeedVariant(DEFAULT_PARAMS);
    // Staked threshold
    expect(src).toContain('STAKED 91 DYADIC.>');
    // Penalty threshold
    expect(src).toContain('50 DYADIC.<');
    // Bull outputs (DD=1e18, AW=20, AS=1e18, CI=0)
    expect(src).toContain('1000000000000000000 20 1000000000000000000 0');
    // Bear outputs (DD=0.3e18, AW=100, AS=0.3e18, CI=0)
    expect(src).toContain('300000000000000000 100 300000000000000000 0');
  });

  it('different staked thresholds produce distinct programs', () => {
    const programs = STAKED_THRESHOLDS.map(st =>
      generateSeedVariant({ ...DEFAULT_PARAMS, stakedThreshold: st }),
    );
    const unique = new Set(programs);
    expect(unique.size).toBe(STAKED_THRESHOLDS.length);
  });

  it('different tax distributions produce distinct programs', () => {
    const programs = TAX_DISTRIBUTIONS.map(td =>
      generateSeedVariant({ ...DEFAULT_PARAMS, taxDistribution: td }),
    );
    const unique = new Set(programs);
    expect(unique.size).toBe(TAX_DISTRIBUTIONS.length);
  });
});

// ---------------------------------------------------------------------------
// enumerateVariants
// ---------------------------------------------------------------------------

describe('enumerateVariants', () => {
  it('returns 1152 combinations', () => {
    const all = enumerateVariants();
    const expected =
      STAKED_THRESHOLDS.length *
      PENALTY_THRESHOLDS.length *
      BULL_VARIANTS.length *
      BEAR_VARIANTS.length *
      TAX_DISTRIBUTIONS.length;
    expect(all).toHaveLength(expected);
  });

  it('all combinations are distinct', () => {
    const all = enumerateVariants();
    const keys = new Set(
      all.map(v =>
        JSON.stringify([
          v.stakedThreshold,
          v.penaltyThreshold,
          v.bull.anchorWidth,
          v.bear.anchorWidth,
          v.taxDistribution,
        ]),
      ),
    );
    expect(keys.size).toBe(all.length);
  });

  it('covers all staked thresholds', () => {
    const all = enumerateVariants();
    const seen = new Set(all.map(v => v.stakedThreshold));
    for (const st of STAKED_THRESHOLDS) {
      expect(seen.has(st)).toBe(true);
    }
  });

  it('covers all tax distributions', () => {
    const all = enumerateVariants();
    const seen = new Set(all.map(v => v.taxDistribution));
    for (const td of TAX_DISTRIBUTIONS) {
      expect(seen.has(td)).toBe(true);
    }
  });
});

// ---------------------------------------------------------------------------
// selectVariants
// ---------------------------------------------------------------------------

describe('selectVariants', () => {
  it('returns exactly n variants for n < total', () => {
    expect(selectVariants(1)).toHaveLength(1);
    expect(selectVariants(10)).toHaveLength(10);
    expect(selectVariants(50)).toHaveLength(50);
  });

  it('returns all variants when n >= total', () => {
    const total = enumerateVariants().length;
    expect(selectVariants(total)).toHaveLength(total);
    expect(selectVariants(total + 100)).toHaveLength(total);
  });

  it('throws for n < 1', () => {
    expect(() => selectVariants(0)).toThrow();
    expect(() => selectVariants(-1)).toThrow();
  });

  it('all selected variants produce valid programs', () => {
    const variants = selectVariants(20);
    for (const v of variants) {
      const src = generateSeedVariant(v);
      expect(parseAndValidate(src)).toBe(true);
    }
  });

  it('n=6 covers all staked thresholds via even stride', () => {
    // enumerateVariants puts STAKED_THRESHOLDS outermost (192 entries per threshold).
    // stride = 1152/6 = 192 → one representative per staked% value.
    const variants = selectVariants(6);
    expect(variants).toHaveLength(6);
    const stakedValues = new Set(variants.map(v => v.stakedThreshold));
    expect(stakedValues.size).toBe(STAKED_THRESHOLDS.length);
    for (const st of STAKED_THRESHOLDS) {
      expect(stakedValues.has(st)).toBe(true);
    }
    // Each representative is a valid Push3 program
    for (const v of variants) {
      expect(parseAndValidate(generateSeedVariant(v))).toBe(true);
    }
  });

  it('produces diverse programs (no duplicates in typical pop size)', () => {
    const variants = selectVariants(24);
    const programs = variants.map(v => generateSeedVariant(v));
    const unique = new Set(programs);
    expect(unique.size).toBe(24);
  });
});
