// Wave 6 — combat intelligence: archetype registry cross-file match,
// classify('mma') blends from fixtures (thin data → fewer claims, never
// fabricated), and styleMatchup honesty. NO network.

const svc = require('../../src/services/archetypeService');
const arch = require('../../web/src/lib/archetypes');

describe('combat archetype registry — pinned + cross-file agreement', () => {
  const PINNED = {
    STRIKER:  { color: '#E8703A', glyph: '✦' },
    GRAPPLER: { color: '#2FA4E7', glyph: '⊗' },
    PRESSURE: { color: '#E4574C', glyph: '➤' },
    COUNTER:  { color: '#8E7BE0', glyph: '◊' },
    FINISHER: { color: '#12B886', glyph: '▲' },
    GRINDER:  { color: '#B0883B', glyph: '▦' },
  };

  it('backend COMBAT_ARCHETYPES carries exactly the six pinned styles', () => {
    expect(Object.keys(svc.COMBAT_ARCHETYPES).sort()).toEqual(Object.keys(PINNED).sort());
  });

  it('backend colors + glyphs match the pinned spec', () => {
    for (const [name, p] of Object.entries(PINNED)) {
      expect(svc.COMBAT_ARCHETYPES[name].color).toBe(p.color);
      expect(svc.COMBAT_ARCHETYPES[name].glyph).toBe(p.glyph);
    }
  });

  it('frontend COMBAT_ARCHETYPE_MAP colors + glyph chars MATCH the backend', () => {
    for (const [name, a] of Object.entries(svc.COMBAT_ARCHETYPES)) {
      const front = arch.COMBAT_ARCHETYPE_MAP[name];
      expect(front).toBeDefined();
      expect(front.c).toBe(a.color);
      expect(front.char).toBe(a.glyph);
    }
    // No extra frontend combat archetypes beyond the pinned six.
    expect(Object.keys(arch.COMBAT_ARCHETYPE_MAP).sort()).toEqual(Object.keys(svc.COMBAT_ARCHETYPES).sort());
  });

  it('combat FINISHER is namespaced — it does NOT collide with the soccer FINISHER', () => {
    // Soccer FINISHER stays #FF5C5C in the shared map; combat FINISHER is #12B886.
    expect(arch.ARCHETYPE_MAP.FINISHER.c).toBe('#FF5C5C');
    expect(arch.combatArchetypeColor('FINISHER')).toBe('#12B886');
    // sport-aware resolution keeps them apart:
    expect(arch.archetypeColor('FINISHER')).toBe('#FF5C5C');          // no sport → soccer
    expect(arch.archetypeColor('FINISHER', 'mma')).toBe('#12B886');   // mma → combat
  });
});

describe("classify('mma', …) — blends from fixtures", () => {
  it('a high-volume distance striker profiles STRIKER-primary', () => {
    const r = svc.classify('mma', { slpm: 6, sapm: 3, strAcc: 0.55, strDef: 0.62, tdAvg: 0.2, koRate: 0.6, decRate: 0.3 });
    expect(r.primary && r.primary.name).toBe('STRIKER');
    expect(r.blend.length).toBeGreaterThan(0);
    expect(r.blend.map((b) => b.archetype)).toContain('STRIKER');
  });

  it('a takedown-heavy submission threat profiles GRAPPLER-primary', () => {
    const r = svc.classify('mma', { tdAvg: 4.5, subAvg: 1.8, slpm: 2.5, sapm: 2, strDef: 0.5, koRate: 0.1, subRate: 0.5, decRate: 0.4 });
    expect(r.primary && r.primary.name).toBe('GRAPPLER');
  });

  it('a high-finish record profiles FINISHER via method rates', () => {
    const r = svc.classify('mma', { koWins: 10, subWins: 5, decWins: 1, totalWins: 16 });
    expect(r.blend.map((b) => b.archetype)).toContain('FINISHER');
  });

  it('THIN data yields NO fabricated style — empty blend, null primary (honest)', () => {
    const r = svc.classify('mma', { record: '9-4-0' }); // no strike/td/method inputs
    expect(r.blend).toEqual([]);
    expect(r.primary).toBeNull();
    expect(r.secondary).toBeNull();
  });

  it('an absent stat never scores an axis (Number(null) === 0 guard)', () => {
    const r = svc.classify('mma', { slpm: null, tdAvg: undefined });
    expect(r.blend).toEqual([]);
  });
});

describe('styleMatchup — honest MODEL read, never a fabricated grade', () => {
  const striker = svc.classify('mma', { slpm: 6, sapm: 3, strAcc: 0.55, tdAvg: 0.2, koRate: 0.6 });
  const grappler = svc.classify('mma', { tdAvg: 4.5, subAvg: 1.8, slpm: 2.5, subRate: 0.5, decRate: 0.4 });

  it('divergent styles produce a clear edge to one side (no confidence %)', () => {
    const v = svc.styleMatchup(striker, grappler);
    expect(v.verdict).toMatch(/EDGE$/);
    expect(['a', 'b']).toContain(v.edgeSide);
    expect(v).not.toHaveProperty('edge');       // no fabricated edge %
    expect(v).not.toHaveProperty('confidence');  // no fabricated confidence
  });

  it('thin data on either side → INSUFFICIENT READ', () => {
    expect(svc.styleMatchup(striker, svc.classify('mma', {})).verdict).toBe('INSUFFICIENT READ');
    expect(svc.styleMatchup([], grappler).verdict).toBe('INSUFFICIENT READ');
  });

  it('two near-identical style blends → STYLES EVEN, no edge side', () => {
    const v = svc.styleMatchup(striker, striker);
    expect(v.verdict).toBe('STYLES EVEN');
    expect(v.edgeSide).toBeNull();
  });

  it('accepts raw blend arrays as well as classify() results', () => {
    const v = svc.styleMatchup(striker.blend, grappler.blend);
    expect(v.verdict).toMatch(/EDGE$|EVEN|INSUFFICIENT/);
  });
});
