import { describe, expect, test } from 'bun:test';
import {
  GET_RECENT_SALIENCE_DESCRIPTION,
  FIND_ANOMALIES_DESCRIPTION,
  GET_RECENT_TRANSCRIPTS_DESCRIPTION,
  LIST_PAGES_DESCRIPTION,
  QUERY_DESCRIPTION,
  SEARCH_DESCRIPTION,
  LIST_SKILLS_DESCRIPTION,
  GET_SKILL_DESCRIPTION,
  SKILL_CATALOG_INSTRUCTIONS,
  SKILL_CLIENT_GUIDANCE,
} from '../src/core/operations-descriptions.ts';
import { operations, operationsByName } from '../src/core/operations.ts';
import { BRAIN_TOOL_ALLOWLIST } from '../src/core/minions/tools/brain-allowlist.ts';

/**
 * Tool descriptions are LLM-facing strings that drive routing. v0.29 adds
 * three new ops + redirects on three existing ones. These tests pin the
 * key phrases that the routing decision depends on so accidental edits
 * (description rewrites, AI cleanup, voice changes) fail CI.
 */

describe('v0.29 — get_recent_salience description', () => {
  test('matches the operation registration', () => {
    expect(operationsByName['get_recent_salience'].description).toBe(GET_RECENT_SALIENCE_DESCRIPTION);
  });

  test('contains the explicit "Use this when" trigger phrase', () => {
    expect(GET_RECENT_SALIENCE_DESCRIPTION).toContain("Use this when the user asks");
  });

  test('lists the personal-query trigger keywords', () => {
    expect(GET_RECENT_SALIENCE_DESCRIPTION).toContain("what's been going on");
    expect(GET_RECENT_SALIENCE_DESCRIPTION).toContain("anything crazy happening");
    expect(GET_RECENT_SALIENCE_DESCRIPTION).toContain("notable");
  });

  test('explicitly bans semantic search for the same intent', () => {
    expect(GET_RECENT_SALIENCE_DESCRIPTION).toContain("Do NOT run a semantic search for these");
  });
});

describe('v0.29 — find_anomalies description', () => {
  test('matches the operation registration', () => {
    expect(operationsByName['find_anomalies'].description).toBe(FIND_ANOMALIES_DESCRIPTION);
  });

  test('mentions the cohort framing', () => {
    expect(FIND_ANOMALIES_DESCRIPTION).toContain("grouped by cohort");
    expect(FIND_ANOMALIES_DESCRIPTION).toContain("(tag or type)");
  });

  test('lists the unusual / what-stood-out trigger phrases', () => {
    expect(FIND_ANOMALIES_DESCRIPTION).toContain("stood out");
    expect(FIND_ANOMALIES_DESCRIPTION).toContain("unusual");
  });

  test('does not promise year-cohort behavior (deferred to v0.30)', () => {
    // v0.29 ships tag + type only. The phrase below confirms the description
    // does not lie about coverage — surfacing year would route the LLM to
    // call the op for date-bucket questions it can't actually serve.
    expect(FIND_ANOMALIES_DESCRIPTION).toContain("Cohort kinds: tag, type");
  });
});

describe('v0.29 — get_recent_transcripts description', () => {
  test('matches the operation registration', () => {
    expect(operationsByName['get_recent_transcripts'].description).toBe(GET_RECENT_TRANSCRIPTS_DESCRIPTION);
  });

  test('mandates priority over query/search for personal questions', () => {
    expect(GET_RECENT_TRANSCRIPTS_DESCRIPTION).toContain("FIRST");
  });

  test('explains raw vs polished distinction', () => {
    expect(GET_RECENT_TRANSCRIPTS_DESCRIPTION).toContain("NOT polished");
    expect(GET_RECENT_TRANSCRIPTS_DESCRIPTION).toContain("canonical source");
  });

  test('discloses the local-only constraint to the LLM', () => {
    expect(GET_RECENT_TRANSCRIPTS_DESCRIPTION).toContain("Local-only");
    expect(GET_RECENT_TRANSCRIPTS_DESCRIPTION).toContain("permission_denied");
  });
});

describe('v0.29 — redirect hints on existing ops', () => {
  test('list_pages mentions sort=updated_desc as the recency-question answer', () => {
    expect(operationsByName['list_pages'].description).toBe(LIST_PAGES_DESCRIPTION);
    expect(LIST_PAGES_DESCRIPTION).toContain("sort=updated_desc");
    expect(LIST_PAGES_DESCRIPTION).toContain("what did I touch this week");
  });

  test('query redirects personal/emotional queries to the v0.29 ops', () => {
    expect(operationsByName['query'].description).toBe(QUERY_DESCRIPTION);
    expect(QUERY_DESCRIPTION).toContain("get_recent_salience");
    expect(QUERY_DESCRIPTION).toContain("find_anomalies");
    expect(QUERY_DESCRIPTION).toContain("get_recent_transcripts");
  });

  test('query warns the LLM not to assume "crazy" means impressive', () => {
    expect(QUERY_DESCRIPTION).toContain("Do NOT assume");
    expect(QUERY_DESCRIPTION).toContain("difficult or emotionally charged");
  });

  test('search has the shorter redirect hint', () => {
    expect(operationsByName['search'].description).toBe(SEARCH_DESCRIPTION);
    expect(SEARCH_DESCRIPTION).toContain("get_recent_salience");
  });
});

describe('v0.29 — subagent allow-list', () => {
  test('includes get_recent_salience and find_anomalies', () => {
    expect(BRAIN_TOOL_ALLOWLIST.has('get_recent_salience')).toBe(true);
    expect(BRAIN_TOOL_ALLOWLIST.has('find_anomalies')).toBe(true);
  });

  test('excludes get_recent_transcripts (codex C3 — would be a remote=true footgun)', () => {
    // The op throws permission_denied for remote=true callers, and all subagent
    // calls run with remote=true. Including it in the allow-list would mean
    // every subagent call to it returns an error — looks like a bug.
    expect(BRAIN_TOOL_ALLOWLIST.has('get_recent_transcripts')).toBe(false);
  });

  test('all v0.29 ops in the allow-list resolve to a registered Operation', () => {
    // brain-allowlist invariant: every name maps to an entry in operations.ts
    // OPERATIONS array. This guard catches rename drift.
    for (const name of ['get_recent_salience', 'find_anomalies']) {
      expect(operationsByName[name]).toBeDefined();
    }
  });

  test('list_pages has new sort + updated_after params surfaced to MCP', () => {
    const op = operationsByName['list_pages'];
    expect(op.params.sort).toBeDefined();
    expect(op.params.updated_after).toBeDefined();
  });
});

describe('v0.29 — operations array carries the three new ops', () => {
  test('all three are registered (one allow-listed pair + one local-only)', () => {
    const names = operations.map(o => o.name);
    expect(names).toContain('get_recent_salience');
    expect(names).toContain('find_anomalies');
    expect(names).toContain('get_recent_transcripts');
  });
});

describe('PR1 — skill catalog descriptions', () => {
  test('list_skills / get_skill match the operation registration', () => {
    expect(operationsByName['list_skills'].description).toBe(LIST_SKILLS_DESCRIPTION);
    expect(operationsByName['get_skill'].description).toBe(GET_SKILL_DESCRIPTION);
  });

  test('descriptions teach "prose, not executable code" + the follow-the-prose protocol', () => {
    expect(LIST_SKILLS_DESCRIPTION).toContain('NOT executable code');
    expect(LIST_SKILLS_DESCRIPTION).toContain('get_skill');
    expect(GET_SKILL_DESCRIPTION).toContain('nothing to');
    expect(GET_SKILL_DESCRIPTION).toContain('same-named MCP tool');
  });

  test('descriptions surface usable/unavailable tool honesty', () => {
    expect(LIST_SKILLS_DESCRIPTION).toContain('usable_tools');
    expect(LIST_SKILLS_DESCRIPTION).toContain('unavailable_tools');
    expect(GET_SKILL_DESCRIPTION).toContain('unavailable_tools');
  });

  test('both ops are read-scope, non-localOnly (thin clients reach them over HTTP)', () => {
    for (const n of ['list_skills', 'get_skill']) {
      expect(operationsByName[n].scope).toBe('read');
      expect(operationsByName[n].localOnly).toBeFalsy();
      expect(operationsByName[n].mutating).toBeFalsy();
    }
  });

  test('instructions envelope is shaped + load-bearing', () => {
    expect(SKILL_CATALOG_INSTRUCTIONS.summary).toContain('not executable tools');
    expect(SKILL_CATALOG_INSTRUCTIONS.how_to_use.length).toBeGreaterThanOrEqual(3);
    expect(SKILL_CATALOG_INSTRUCTIONS.how_to_use.join(' ')).toContain('get_skill');
    expect(SKILL_CLIENT_GUIDANCE.nature).toContain('not code to execute');
    expect(SKILL_CLIENT_GUIDANCE.protocol.length).toBeGreaterThanOrEqual(3);
  });

  test('operations array carries both new ops', () => {
    const names = operations.map(o => o.name);
    expect(names).toContain('list_skills');
    expect(names).toContain('get_skill');
  });
});
