import * as fs from 'fs';
import * as path from 'path';
import { tmpdir } from 'os';
import { describe, expect, it, afterEach, vi } from 'vitest';
import { SkillCatalog } from '../skills.js';
import { executeSkillTool } from './skills.js';
import type { ToolContext } from './core.js';

// Failure injection for the swap step: renameSync throws once when the
// destination matches, everything else passes through to the real fs.
const swapState = vi.hoisted(() => ({ failRenameToSuffix: null as string | null }));

vi.mock('fs', async (importOriginal) => {
  const actual = await importOriginal<typeof import('fs')>();
  return {
    ...actual,
    renameSync: (src: fs.PathLike, dst: fs.PathLike) => {
      if (swapState.failRenameToSuffix && String(dst).endsWith(swapState.failRenameToSuffix)) {
        swapState.failRenameToSuffix = null;
        throw new Error('simulated EIO on rename');
      }
      return actual.renameSync(src, dst);
    },
  };
});

function makeTempDir(): string {
  return fs.mkdtempSync(path.join(tmpdir(), 'skill-swap-test-'));
}

function writeSkill(dir: string, filename: string, content: string): void {
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(path.join(dir, filename), content, 'utf-8');
}

const DIR_SKILL = `---
name: tdd
description: TDD workflow
---

## Steps
`;

describe('materializeSkill swap failure', () => {
  const dirs: string[] = [];
  afterEach(() => {
    swapState.failRenameToSuffix = null;
    for (const d of dirs) fs.rmSync(d, { recursive: true, force: true });
    dirs.length = 0;
  });

  it('restores the previous copy when the swap rename fails', () => {
    const systemDir = makeTempDir();
    const userRoot = makeTempDir();
    const workspace = makeTempDir();
    dirs.push(systemDir, userRoot, workspace);

    const skillDir = path.join(systemDir, 'tdd');
    writeSkill(skillDir, 'SKILL.md', DIR_SKILL);
    writeSkill(path.join(skillDir, 'scripts'), 'run.sh', '#!/bin/sh\necho v1\n');

    const catalog = new SkillCatalog(systemDir, userRoot);
    const ctx = { workspacePath: workspace, editAllowed: false, skillCatalog: catalog, userId: 'user1' } as ToolContext;

    const first = executeSkillTool('ReadSkill', { name: 'tdd' }, ctx);
    expect(first!.isError).toBe(false);

    // Source updated; the tmp→dest swap will fail once (e.g. transient FS error).
    writeSkill(path.join(skillDir, 'scripts'), 'run.sh', '#!/bin/sh\necho v2\n');
    swapState.failRenameToSuffix = `${path.sep}skills${path.sep}tdd`;

    const second = executeSkillTool('ReadSkill', { name: 'tdd' }, ctx);
    expect(second!.isError).toBe(false);
    expect(second!.output).toContain('コピーできませんでした');
    // The previous copy must be back in place, not lost.
    const copied = fs.readFileSync(path.join(workspace, 'skills', 'tdd', 'scripts', 'run.sh'), 'utf-8');
    expect(copied).toContain('v1');
  });
});
