/**
 * Unit tests for skills install CLAUDE.md generation
 */

import { buildSkillsSection, ensureClaudeMdSkillsInstructions } from '../install';

// Mock dependencies
jest.mock('../../../core/version', () => ({
  getSelfVersion: () => '1.0.0-test',
}));

jest.mock('../catalog', () => ({
  loadCatalog: () => ({
    version: 1,
    skills: [
      { name: 'skill-alpha', description: 'Use when you want to build alpha components', tags: [], category: 'dev' },
      { name: 'skill-beta', description: 'Testing helpers and utilities', tags: [], category: 'test' },
    ],
  }),
  getCatalogDir: () => '/mock/catalog',
}));

let mockFiles: Record<string, string> = {};
let mockDirs: Set<string> = new Set();

jest.mock('@frontmcp/utils', () => ({
  fileExists: jest.fn(async (p: string) => p in mockFiles || mockDirs.has(p)),
  readFile: jest.fn(async (p: string) => mockFiles[p] ?? ''),
  writeFile: jest.fn(async (p: string, content: string) => {
    mockFiles[p] = content;
  }),
  ensureDir: jest.fn(),
  cp: jest.fn(),
  readdir: jest.fn(async (dir: string) => {
    const prefix = dir.endsWith('/') ? dir : `${dir}/`;
    const children = new Set<string>();
    for (const file of Object.keys(mockFiles)) {
      if (file.startsWith(prefix)) {
        const rest = file.slice(prefix.length);
        const firstSegment = rest.split('/')[0];
        if (firstSegment) children.add(firstSegment);
      }
    }
    return Array.from(children).sort();
  }),
}));

/** Helper: mark both catalog skills as installed under cwd */
function mockInstalledSkills(cwd: string) {
  mockDirs.add(`${cwd}/.claude/skills`);
  mockFiles[`${cwd}/.claude/skills/skill-alpha/SKILL.md`] = '# skill-alpha';
  mockFiles[`${cwd}/.claude/skills/skill-beta/SKILL.md`] = '# skill-beta';
}

beforeEach(() => {
  mockFiles = {};
  mockDirs = new Set();
  jest.clearAllMocks();
});

describe('buildSkillsSection', () => {
  const skills = [
    { name: 'frontmcp-development', description: 'Use when you want to create tools, resources, and prompts' },
    { name: 'frontmcp-testing', description: 'Testing utilities and e2e helpers' },
  ];

  it('should generate section with version markers', () => {
    const section = buildSkillsSection('1.2.3', skills);

    expect(section).toContain('<!-- frontmcp:skills-start v1.2.3 -->');
    expect(section).toContain('<!-- frontmcp:skills-end -->');
  });

  it('should include auto-generated attribution with version', () => {
    const section = buildSkillsSection('1.2.3', skills);

    expect(section).toContain('Auto-generated by `frontmcp skills install` (v1.2.3)');
    expect(section).toContain('do not edit manually');
  });

  it('should list all skills from input', () => {
    const section = buildSkillsSection('1.0.0', skills);

    expect(section).toContain('**frontmcp-development**');
    expect(section).toContain('**frontmcp-testing**');
  });

  it('should strip "Use when you want to" prefix from descriptions', () => {
    const section = buildSkillsSection('1.0.0', skills);

    expect(section).toContain('create tools, resources, and prompts');
    expect(section).not.toContain('Use when you want to create');
  });

  it('should include the Skills and Tools heading', () => {
    const section = buildSkillsSection('1.0.0', skills);

    expect(section).toContain('# Skills and Tools');
  });

  it('should handle empty skills list', () => {
    const section = buildSkillsSection('1.0.0', []);

    expect(section).toContain('<!-- frontmcp:skills-start');
    expect(section).toContain('<!-- frontmcp:skills-end -->');
    expect(section).not.toContain('- **');
  });
});

describe('ensureClaudeMdSkillsInstructions', () => {
  it('should create new CLAUDE.md with installed skills only', async () => {
    mockInstalledSkills('/test/project');

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toBeDefined();
    expect(content).toContain('<!-- frontmcp:skills-start v1.0.0-test -->');
    expect(content).toContain('<!-- frontmcp:skills-end -->');
    expect(content).toContain('**skill-alpha**');
    expect(content).toContain('**skill-beta**');
  });

  it('should only list installed skills, not full catalog', async () => {
    // Only install skill-alpha, not skill-beta
    mockDirs.add('/test/project/.claude/skills');
    mockFiles['/test/project/.claude/skills/skill-alpha/SKILL.md'] = '# skill-alpha';

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toContain('**skill-alpha**');
    expect(content).not.toContain('**skill-beta**');
  });

  it('should replace existing marker-bounded block on re-run', async () => {
    mockInstalledSkills('/test/project');
    mockFiles['/test/project/CLAUDE.md'] = [
      '# My Project',
      '',
      '<!-- frontmcp:skills-start v0.9.0 -->',
      '# Skills and Tools',
      'Old content here',
      '<!-- frontmcp:skills-end -->',
      '',
      '## Other Section',
      'User content preserved.',
    ].join('\n');

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toContain('<!-- frontmcp:skills-start v1.0.0-test -->');
    expect(content).not.toContain('v0.9.0');
    expect(content).not.toContain('Old content here');
    expect(content).toContain('**skill-alpha**');
    expect(content).toContain('## Other Section');
    expect(content).toContain('User content preserved.');
  });

  it('should migrate legacy "# Skills and Tools" header to marker format', async () => {
    mockInstalledSkills('/test/project');
    mockFiles['/test/project/CLAUDE.md'] = [
      '# Skills and Tools',
      'Some old hardcoded content.',
      '',
      '# Other Heading',
      'User content here.',
    ].join('\n');

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toContain('<!-- frontmcp:skills-start v1.0.0-test -->');
    expect(content).toContain('<!-- frontmcp:skills-end -->');
    expect(content).not.toContain('Some old hardcoded content');
    expect(content).toContain('# Other Heading');
    expect(content).toContain('User content here.');
  });

  it('should migrate legacy "# Skills and Tools" when next heading is ## level', async () => {
    mockInstalledSkills('/test/project');
    mockFiles['/test/project/CLAUDE.md'] = [
      '# Skills and Tools',
      'Some old hardcoded content.',
      '',
      '## Other Heading',
      'User content here.',
    ].join('\n');

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toContain('<!-- frontmcp:skills-start v1.0.0-test -->');
    expect(content).toContain('<!-- frontmcp:skills-end -->');
    expect(content).not.toContain('Some old hardcoded content');
    expect(content).toContain('## Other Heading');
    expect(content).toContain('User content here.');
  });

  it('should prepend block when CLAUDE.md exists without any skills section', async () => {
    mockInstalledSkills('/test/project');
    mockFiles['/test/project/CLAUDE.md'] = ['# My Project', '', '## Commands', 'Run yarn dev.'].join('\n');

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    // Skills block should be at the top
    expect(content.indexOf('<!-- frontmcp:skills-start')).toBeLessThan(content.indexOf('# My Project'));
    // Original content preserved
    expect(content).toContain('## Commands');
    expect(content).toContain('Run yarn dev.');
  });

  it('should not produce duplicate marker blocks', async () => {
    mockInstalledSkills('/test/project');
    // Run twice
    await ensureClaudeMdSkillsInstructions('/test/project');
    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    const startCount = (content.match(/<!-- frontmcp:skills-start/g) ?? []).length;
    const endCount = (content.match(/<!-- frontmcp:skills-end -->/g) ?? []).length;
    expect(startCount).toBe(1);
    expect(endCount).toBe(1);
  });

  it('should produce empty skills list when no skills are installed', async () => {
    // No skills installed (no SKILL.md files in mockFiles)
    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toContain('<!-- frontmcp:skills-start');
    expect(content).not.toContain('**skill-alpha**');
    expect(content).not.toContain('**skill-beta**');
  });

  it('should include project-defined (non-catalog) skills by reading their frontmatter', async () => {
    mockDirs.add('/test/project/.claude/skills');
    mockFiles['/test/project/.claude/skills/skill-alpha/SKILL.md'] = '# skill-alpha';
    // A project-installed skill whose name is NOT in the catalog: its
    // description must come from the SKILL.md frontmatter, not the catalog.
    mockFiles['/test/project/.claude/skills/example-project/SKILL.md'] = [
      '---',
      'name: example-project',
      'description: "Conventions for editing files in the example project"',
      '---',
      '',
      '# Example Project',
    ].join('\n');

    await ensureClaudeMdSkillsInstructions('/test/project');

    const content = mockFiles['/test/project/CLAUDE.md'];
    expect(content).toContain('**skill-alpha**');
    expect(content).toContain('**example-project**');
    expect(content).toContain('Conventions for editing files in the example project');
  });
});
