/**
 * Verify: check-resolvable merges entries from all resolver files.
 *
 * The common OpenClaw layout has:
 *   workspace/AGENTS.md       — the real dispatcher (200+ entries)
 *   workspace/skills/RESOLVER.md — thin skillpack-installed subset (~40 entries)
 *
 * Before this fix, RESOLVER.md won by first-match policy, so 160+ skills
 * showed as "unreachable" even though AGENTS.md had routing for them.
 *
 * After: entries from both files are merged (deduped by skillPath).
 */
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { checkResolvable, parseResolverEntries } from '../src/core/check-resolvable.ts';
import { findAllResolverFiles } from '../src/core/resolver-filenames.ts';

// ---------------------------------------------------------------------------
// findAllResolverFiles
// ---------------------------------------------------------------------------

describe('findAllResolverFiles', () => {
  let dir: string;

  beforeAll(() => {
    dir = mkdtempSync(join(tmpdir(), 'resolver-merge-'));
  });
  afterAll(() => {
    rmSync(dir, { recursive: true, force: true });
  });

  it('returns empty when no resolver files exist', () => {
    expect(findAllResolverFiles(dir)).toEqual([]);
  });

  it('returns RESOLVER.md when only it exists', () => {
    writeFileSync(join(dir, 'RESOLVER.md'), '# test');
    const files = findAllResolverFiles(dir);
    expect(files).toHaveLength(1);
    expect(files[0]).toEndWith('RESOLVER.md');
    rmSync(join(dir, 'RESOLVER.md'));
  });

  it('returns AGENTS.md when only it exists', () => {
    writeFileSync(join(dir, 'AGENTS.md'), '# test');
    const files = findAllResolverFiles(dir);
    expect(files).toHaveLength(1);
    expect(files[0]).toEndWith('AGENTS.md');
    rmSync(join(dir, 'AGENTS.md'));
  });

  it('returns both when both exist (RESOLVER.md first)', () => {
    writeFileSync(join(dir, 'RESOLVER.md'), '# resolver');
    writeFileSync(join(dir, 'AGENTS.md'), '# agents');
    const files = findAllResolverFiles(dir);
    expect(files).toHaveLength(2);
    expect(files[0]).toEndWith('RESOLVER.md');
    expect(files[1]).toEndWith('AGENTS.md');
    rmSync(join(dir, 'RESOLVER.md'));
    rmSync(join(dir, 'AGENTS.md'));
  });
});

// ---------------------------------------------------------------------------
// Merge behavior in checkResolvable
// ---------------------------------------------------------------------------

describe('checkResolvable merges resolver files', () => {
  let workspace: string;
  let skillsDir: string;

  beforeAll(() => {
    workspace = mkdtempSync(join(tmpdir(), 'resolver-merge-e2e-'));
    skillsDir = join(workspace, 'skills');
    mkdirSync(skillsDir, { recursive: true });

    // Create 3 skills on disk WITHOUT frontmatter triggers — these
    // tests exercise the legacy RESOLVER.md-only authority path. The
    // v0.41.11 auto-registration path is covered by the test below
    // ("with frontmatter triggers only → all 3 reachable via
    // auto-registration").
    for (const name of ['alpha', 'beta', 'gamma']) {
      const skillDir = join(skillsDir, name);
      mkdirSync(skillDir, { recursive: true });
      // Name-only frontmatter; no triggers: so auto-registration is a
      // no-op for these skills and RESOLVER.md is the sole authority.
      writeFileSync(join(skillDir, 'SKILL.md'), `---\nname: ${name}\n---\n# ${name}\n`);
    }
  });

  afterAll(() => {
    rmSync(workspace, { recursive: true, force: true });
  });

  it('with only skills/RESOLVER.md covering 1 of 3 → 2 unreachable', () => {
    writeFileSync(join(skillsDir, 'RESOLVER.md'), `# Resolver\n\n| Trigger | Skill |\n|---|---|\n| do alpha | \`skills/alpha/SKILL.md\` |\n`);
    const report = checkResolvable(skillsDir);
    expect(report.summary.reachable).toBe(1);
    expect(report.summary.unreachable).toBe(2);
    rmSync(join(skillsDir, 'RESOLVER.md'));
  });

  it('v0.41.11: skills with frontmatter triggers are auto-registered (no RESOLVER.md needed)', () => {
    // Rewrite the 3 skills with frontmatter triggers and assert
    // checkResolvable sees all 3 as reachable without ANY RESOLVER.md
    // or AGENTS.md present. This is the new structural contract:
    // frontmatter is authoritative.
    for (const name of ['alpha', 'beta', 'gamma']) {
      writeFileSync(
        join(skillsDir, name, 'SKILL.md'),
        `---\nname: ${name}\ntriggers:\n  - "${name} trigger"\n---\n# ${name}\n`,
      );
    }
    const report = checkResolvable(skillsDir);
    expect(report.summary.reachable).toBe(3);
    expect(report.summary.unreachable).toBe(0);
    // Restore name-only frontmatter so subsequent tests in this
    // describe block see the legacy RESOLVER.md-only setup.
    for (const name of ['alpha', 'beta', 'gamma']) {
      writeFileSync(
        join(skillsDir, name, 'SKILL.md'),
        `---\nname: ${name}\n---\n# ${name}\n`,
      );
    }
  });

  it('with skills/RESOLVER.md (1 skill) + ../AGENTS.md (2 more) → all 3 reachable', () => {
    // Thin RESOLVER.md in skills dir (e.g. from skillpack)
    writeFileSync(join(skillsDir, 'RESOLVER.md'),
      `# Resolver\n\n| Trigger | Skill |\n|---|---|\n| do alpha | \`skills/alpha/SKILL.md\` |\n`);

    // Rich AGENTS.md at workspace root (OpenClaw convention)
    writeFileSync(join(workspace, 'AGENTS.md'),
      `# AGENTS.md\n\n## Skills\n\n| Trigger | Skill |\n|---|---|\n| do beta | \`skills/beta/SKILL.md\` |\n| do gamma | \`skills/gamma/SKILL.md\` |\n`);

    const report = checkResolvable(skillsDir);
    expect(report.summary.reachable).toBe(3);
    expect(report.summary.unreachable).toBe(0);
    expect(report.ok).toBe(true);

    rmSync(join(skillsDir, 'RESOLVER.md'));
    rmSync(join(workspace, 'AGENTS.md'));
  });

  it('deduplicates overlapping entries (first occurrence wins)', () => {
    // Both files reference alpha
    writeFileSync(join(skillsDir, 'RESOLVER.md'),
      `# Resolver\n\n| Trigger | Skill |\n|---|---|\n| do alpha | \`skills/alpha/SKILL.md\` |\n`);
    writeFileSync(join(workspace, 'AGENTS.md'),
      `# AGENTS\n\n| Trigger | Skill |\n|---|---|\n| alpha thing | \`skills/alpha/SKILL.md\` |\n| do beta | \`skills/beta/SKILL.md\` |\n| do gamma | \`skills/gamma/SKILL.md\` |\n`);

    const report = checkResolvable(skillsDir);
    expect(report.summary.reachable).toBe(3);
    expect(report.summary.unreachable).toBe(0);

    rmSync(join(skillsDir, 'RESOLVER.md'));
    rmSync(join(workspace, 'AGENTS.md'));
  });

  it('AGENTS.md at workspace root works alone (no RESOLVER.md)', () => {
    writeFileSync(join(workspace, 'AGENTS.md'),
      `# AGENTS\n\n| Trigger | Skill |\n|---|---|\n| a | \`skills/alpha/SKILL.md\` |\n| b | \`skills/beta/SKILL.md\` |\n| c | \`skills/gamma/SKILL.md\` |\n`);

    const report = checkResolvable(skillsDir);
    expect(report.summary.reachable).toBe(3);
    expect(report.summary.unreachable).toBe(0);

    rmSync(join(workspace, 'AGENTS.md'));
  });
});
