// ABOUTME: Tests for the use_skill tool that activates skills and returns their content

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { UseSkillTool } from '../use-skill-tool';
import { SkillRegistry } from '@lace/agent/skills';

describe('UseSkillTool', () => {
  let tempDir: string;
  let skillsDir: string;
  let registry: SkillRegistry;

  beforeEach(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'use-skill-test-'));
    skillsDir = join(tempDir, 'skills');
    mkdirSync(skillsDir, { recursive: true });
  });

  afterEach(() => {
    rmSync(tempDir, { recursive: true, force: true });
  });

  function createSkill(name: string, description: string, body: string): void {
    const skillDir = join(skillsDir, name);
    mkdirSync(skillDir, { recursive: true });
    writeFileSync(
      join(skillDir, 'SKILL.md'),
      `---\nname: ${name}\ndescription: ${description}\n---\n\n${body}`
    );
  }

  it('returns skill content for existing skill without leaking the host path', async () => {
    createSkill('commit', 'Create git commits', '# Commit Skill\n\nFollow these steps...');
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    const result = await tool.execute(
      { skill: 'commit' },
      { signal: new AbortController().signal }
    );

    expect(result.status).toBe('completed');
    expect(result.content).toHaveLength(1);
    const text = result.content[0];
    expect(text.type).toBe('text');
    const textContent = (text as { type: 'text'; text: string }).text;
    expect(textContent).toContain('Skill: commit');
    // The skill dir is a container-internal absolute path that is noise (and
    // sometimes unusable) to the agent — it must not be emitted.
    expect(textContent).not.toContain('Location:');
    expect(textContent).not.toContain(skillsDir);
    expect(textContent).toContain('# Commit Skill');
    expect(textContent).toContain('Follow these steps...');
  });

  it('returns error for unknown skill', async () => {
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    const result = await tool.execute(
      { skill: 'nonexistent' },
      { signal: new AbortController().signal }
    );

    expect(result.status).toBe('failed');
    expect(result.content).toHaveLength(1);
    const text = (result.content[0] as { type: 'text'; text: string }).text;
    expect(text).toContain('not found');
    expect(text).toContain('nonexistent');
  });

  it('returns validation error for empty skill name', async () => {
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    const result = await tool.execute({ skill: '' }, { signal: new AbortController().signal });

    expect(result.status).toBe('failed');
    // Zod validation should catch this
    const text = (result.content[0] as { type: 'text'; text: string }).text;
    expect(text).toContain('skill');
  });

  it('has correct metadata', () => {
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    expect(tool.name).toBe('use_skill');
    expect(tool.description).toContain('skill');
    expect(tool.annotations?.readOnlyHint).toBe(true);
    expect(tool.annotations?.idempotentHint).toBe(true);
  });

  it('refreshes registry on cache miss and finds newly-added skill', async () => {
    // Registry built with an empty skillsDir, then a skill is added afterward.
    // UseSkillTool should refresh on miss and discover it.
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    createSkill('fresh', 'A freshly-authored skill', '# Fresh\n\nBody.');

    const result = await tool.execute({ skill: 'fresh' }, { signal: new AbortController().signal });

    expect(result.status).toBe('completed');
    const text = (result.content[0] as { type: 'text'; text: string }).text;
    expect(text).toContain('Skill: fresh');
    expect(text).toContain('# Fresh');
  });

  it('still reports not-found after refresh when skill truly does not exist', async () => {
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    const result = await tool.execute(
      { skill: 'never-existed' },
      { signal: new AbortController().signal }
    );

    expect(result.status).toBe('failed');
    const text = (result.content[0] as { type: 'text'; text: string }).text;
    expect(text).toContain('not found');
  });

  it('includes skill body exactly as written', async () => {
    const complexBody = `# Complex Skill

## Section 1

- Item one
- Item two

\`\`\`typescript
const x = 1;
\`\`\`

Some **markdown** content with *formatting*.`;

    createSkill('complex', 'A complex skill', complexBody);
    registry = new SkillRegistry({ skillDirs: [skillsDir] });
    const tool = new UseSkillTool(registry);

    const result = await tool.execute(
      { skill: 'complex' },
      { signal: new AbortController().signal }
    );

    expect(result.status).toBe('completed');
    const text = (result.content[0] as { type: 'text'; text: string }).text;
    expect(text).toContain('## Section 1');
    expect(text).toContain('- Item one');
    expect(text).toContain('const x = 1;');
    expect(text).toContain('Some **markdown** content');
  });
});
