import { describe, expect, it } from 'vitest';
import { getContent } from '@/content';
import { decodeAnswers, encodeAnswers } from '@/lib/code';
import { priorityComplement, scoreProfile, weakest } from '@/lib/scoring';
import { createReportToken, verifyReportToken } from '@/lib/token';
import { ARCHETYPE_IDS, type Answer, type ArchetypeId } from '@/lib/types';

const { questions, archetypes } = getContent();

/** Rates `favourite` 5 and everything else `others`; picks `favourite` in every scenario. */
function answersFavouring(favourite: ArchetypeId, others = 2): Answer[] {
  return questions.map((q) => (q.kind === 'likert' ? (q.archetype === favourite ? 5 : others) : favourite));
}

describe('question set', () => {
  it('has 24 questions with unique ids', () => {
    expect(questions).toHaveLength(24);
    expect(new Set(questions.map((q) => q.id)).size).toBe(24);
  });

  it('rates every archetype exactly twice', () => {
    for (const id of ARCHETYPE_IDS) {
      expect(questions.filter((q) => q.kind === 'likert' && q.archetype === id)).toHaveLength(2);
    }
  });

  it('offers every archetype exactly once in each of the 8 scenarios', () => {
    const scenarios = questions.filter((q) => q.kind === 'scenario');
    expect(scenarios).toHaveLength(8);
    for (const q of scenarios) {
      expect([...q.options.map((o) => o.archetype)].sort()).toEqual([...ARCHETYPE_IDS].sort());
    }
  });

  it('gives every archetype the same maximum score', () => {
    const profile = scoreProfile(questions, answersFavouring('architect'));
    expect(new Set(profile.scores.map((s) => s.maxPoints)).size).toBe(1);
  });
});

describe('result codes', () => {
  it('round-trips answers', () => {
    const answers = answersFavouring('broker', 3);
    const code = encodeAnswers(questions, answers);
    expect(code).toHaveLength(25);
    expect(decodeAnswers(questions, code)).toEqual(answers);
  });

  it('rejects malformed codes', () => {
    const code = encodeAnswers(questions, answersFavouring('builder'));
    expect(decodeAnswers(questions, '')).toBeNull();
    expect(decodeAnswers(questions, code.slice(0, -1))).toBeNull();
    expect(decodeAnswers(questions, '9' + code.slice(1))).toBeNull();
    expect(decodeAnswers(questions, '1Z' + code.slice(2))).toBeNull(); // bad Likert answer
    expect(decodeAnswers(questions, code.slice(0, 3) + '7' + code.slice(4))).toBeNull(); // bad scenario answer
  });

  it('refuses to encode an incomplete attempt', () => {
    const answers = answersFavouring('builder');
    answers[5] = undefined as unknown as Answer;
    expect(() => encodeAnswers(questions, answers)).toThrow();
  });
});

describe('scoring', () => {
  it.each(ARCHETYPE_IDS)('identifies a clear %s as the lead', (id) => {
    const profile = scoreProfile(questions, answersFavouring(id));
    expect(profile.lead).toBe(id);
    expect(profile.scores[0].band).toBe('lead');
    expect(profile.scores[0].relative).toBe(100);
    expect(profile.isBlended).toBe(false);
  });

  it('breaks ties on scenario picks, then on the fixed archetype order', () => {
    // Equal Likert everywhere; scenarios split 4/4 between navigator and builder.
    const scenarioIdx = questions.flatMap((q, i) => (q.kind === 'scenario' ? [i] : []));
    const answers: Answer[] = questions.map((q) => (q.kind === 'likert' ? 3 : 'navigator'));
    scenarioIdx.slice(4).forEach((i) => (answers[i] = 'builder'));
    const profile = scoreProfile(questions, answers);
    expect(profile.lead).toBe('navigator');
    expect(profile.supporting).toBe('builder');
    expect(profile.isBlended).toBe(true);
  });

  it('lists the weakest archetypes, lowest first', () => {
    const profile = scoreProfile(questions, answersFavouring('luminary'));
    const gaps = weakest(profile, 3);
    expect(gaps).toHaveLength(3);
    expect(gaps[0].points).toBeLessThanOrEqual(gaps[2].points);
    expect(gaps.map((g) => g.id)).not.toContain('luminary');
  });

  it('picks the lower-scoring of the lead archetype complements', () => {
    const answers = answersFavouring('architect');
    // Make the Builder strong so the Engineer becomes the priority complement.
    questions.forEach((q, i) => {
      if (q.kind === 'likert' && q.archetype === 'builder') answers[i] = 5;
    });
    const profile = scoreProfile(questions, answers);
    expect(priorityComplement(profile, archetypes.architect.complements)).toBe('engineer');
  });
});

describe('report tokens', () => {
  const secret = 'test-secret-at-least-16-chars';
  const code = encodeAnswers(questions, answersFavouring('overseer'));

  it('verifies a token minted for the same code', () => {
    expect(verifyReportToken(code, createReportToken(code, secret), secret)).toBe(true);
  });

  it('rejects tampered, missing or foreign tokens', () => {
    const token = createReportToken(code, secret);
    const other = encodeAnswers(questions, answersFavouring('engineer'));
    expect(verifyReportToken(other, token, secret)).toBe(false);
    expect(verifyReportToken(code, token.slice(0, -1) + (token.endsWith('A') ? 'B' : 'A'), secret)).toBe(false);
    expect(verifyReportToken(code, '', secret)).toBe(false);
    expect(verifyReportToken(code, createReportToken(code, 'a-different-secret-value'), secret)).toBe(false);
  });
});
