import { describe, it, expect } from 'vitest';
import { SkillRegistry } from './registry.js';
import type { Skill } from './types.js';

/**
 * Helper to create a Skill with sensible defaults. The `name` convenience
 * field sets `manifest.name` so callers don't have to nest it every time.
 */
function makeSkill(overrides: Partial<Skill> & { name?: string } = {}): Skill {
  const name = overrides.name ?? overrides.manifest?.name ?? 'test-skill';
  return {
    manifest: {
      name,
      description: 'Test skill',
      version: '1.0.0',
      tier: 'bundled' as const,
      ...overrides.manifest,
    },
    instructions: overrides.instructions ?? '# Test\nDo things.',
    directory: overrides.directory ?? `/fake/path/${name}`,
    available: overrides.available ?? true,
    ...overrides,
  };
}

describe('SkillRegistry', () => {
  // Objective: verify the in-memory skill registry for registration, lookup,
  // listing, filtering, unregistration, and system prompt generation.

  it('registers and retrieves a skill by name', () => {
    // Positive: a registered skill should be returned by get().
    const registry = new SkillRegistry();
    const skill = makeSkill({ name: 'git' });

    registry.register(skill);

    expect(registry.get('git')).toBe(skill);
  });

  it('returns undefined for an unknown skill', () => {
    // Negative: looking up a name that was never registered should yield undefined.
    const registry = new SkillRegistry();

    expect(registry.get('nonexistent')).toBeUndefined();
  });

  it('lists all registered skills', () => {
    // Positive: list() should return every registered skill regardless of availability.
    const registry = new SkillRegistry();
    const skillA = makeSkill({ name: 'skill-a' });
    const skillB = makeSkill({ name: 'skill-b', available: false });

    registry.register(skillA);
    registry.register(skillB);

    const all = registry.list();
    expect(all).toHaveLength(2);
    expect(all.map((s) => s.manifest.name)).toContain('skill-a');
    expect(all.map((s) => s.manifest.name)).toContain('skill-b');
  });

  it('listAvailable returns only available skills', () => {
    // Positive: unavailable skills should be filtered out.
    const registry = new SkillRegistry();
    const available = makeSkill({ name: 'available', available: true });
    const unavailable = makeSkill({ name: 'unavailable', available: false });

    registry.register(available);
    registry.register(unavailable);

    const result = registry.listAvailable();
    expect(result).toHaveLength(1);
    expect(result[0].manifest.name).toBe('available');
  });

  it('replaces an existing skill with the same name', () => {
    // Positive: re-registering a name should overwrite (managed overrides bundled).
    const registry = new SkillRegistry();
    const original = makeSkill({ name: 'web', instructions: '# Original' });
    const replacement = makeSkill({ name: 'web', instructions: '# Replacement' });

    registry.register(original);
    registry.register(replacement);

    expect(registry.list()).toHaveLength(1);
    expect(registry.get('web')!.instructions).toBe('# Replacement');
  });

  it('unregisters a skill by name', () => {
    // Positive: unregister should remove the skill and return true.
    const registry = new SkillRegistry();
    const skill = makeSkill({ name: 'removable' });
    registry.register(skill);

    const result = registry.unregister('removable');

    expect(result).toBe(true);
    expect(registry.get('removable')).toBeUndefined();
    expect(registry.list()).toHaveLength(0);
  });

  it('returns false when unregistering a nonexistent skill', () => {
    // Negative: unregistering a name that doesn't exist should return false.
    const registry = new SkillRegistry();

    expect(registry.unregister('ghost')).toBe(false);
  });

  it('reset replaces all existing skills with a new set', () => {
    const registry = new SkillRegistry();
    registry.register(makeSkill({ name: 'old-one' }));
    registry.register(makeSkill({ name: 'old-two' }));

    registry.reset([
      makeSkill({ name: 'new-one' }),
      makeSkill({ name: 'new-two', available: false }),
    ]);

    expect(registry.get('old-one')).toBeUndefined();
    expect(registry.get('old-two')).toBeUndefined();
    expect(registry.get('new-one')).toBeDefined();
    expect(registry.get('new-two')).toBeDefined();
    expect(registry.list()).toHaveLength(2);
  });

  it('getSystemPromptAdditions returns empty string with no skills', () => {
    // Negative: with nothing registered the prompt additions should be empty.
    const registry = new SkillRegistry();

    expect(registry.getSystemPromptAdditions()).toBe('');
  });

  it('getSystemPromptAdditions formats available skills correctly', () => {
    // Positive: each available skill should appear as a "## Skill: <name>" section.
    const registry = new SkillRegistry();
    registry.register(makeSkill({ name: 'alpha', instructions: 'Alpha instructions.' }));
    registry.register(makeSkill({ name: 'beta', instructions: 'Beta instructions.' }));

    const prompt = registry.getSystemPromptAdditions();

    expect(prompt).toContain('## Skill: alpha');
    expect(prompt).toContain('Alpha instructions.');
    expect(prompt).toContain('## Skill: beta');
    expect(prompt).toContain('Beta instructions.');
  });

  it('getSystemPromptAdditions excludes unavailable skills', () => {
    // Positive: only available skills contribute to the prompt.
    const registry = new SkillRegistry();
    registry.register(makeSkill({ name: 'enabled', instructions: 'Enabled content.' }));
    registry.register(
      makeSkill({ name: 'disabled', instructions: 'Disabled content.', available: false }),
    );

    const prompt = registry.getSystemPromptAdditions();

    expect(prompt).toContain('## Skill: enabled');
    expect(prompt).not.toContain('## Skill: disabled');
    expect(prompt).not.toContain('Disabled content.');
  });

  it('getSkillNames returns names of available skills only', () => {
    // Positive: unavailable skills should not appear in the name list.
    const registry = new SkillRegistry();
    registry.register(makeSkill({ name: 'active', available: true }));
    registry.register(makeSkill({ name: 'dormant', available: false }));

    const names = registry.getSkillNames();

    expect(names).toContain('active');
    expect(names).not.toContain('dormant');
  });
});
