// @vitest-environment node
import { afterEach, describe, expect, it } from 'vitest';
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import {
  patchExtensionOpenClawSelfImports,
  rewriteOpenClawPluginSdkSpecifiers,
  toImportSpecifier,
} from '../../scripts/openclaw-self-import-patch.mjs';

const tempRoots: string[] = [];

async function createTempOpenClawBundle(): Promise<string> {
  const root = await mkdtemp(path.join(tmpdir(), 'clawx-openclaw-self-import-'));
  tempRoots.push(root);
  return root;
}

afterEach(async () => {
  await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
});

describe('openclaw self-import bundle patch', () => {
  it('converts OpenClaw plugin-sdk package specifiers to bundled relative paths', async () => {
    const root = await createTempOpenClawBundle();
    const distDir = path.join(root, 'dist');
    const pluginSdkDir = path.join(distDir, 'plugin-sdk');
    const extensionDir = path.join(distDir, 'extensions', 'codex');
    await mkdir(pluginSdkDir, { recursive: true });
    await mkdir(extensionDir, { recursive: true });
    await writeFile(path.join(pluginSdkDir, 'provider-model-shared.js'), 'export const ok = true;\n');

    const promptOverlayPath = path.join(extensionDir, 'prompt-overlay.js');
    await writeFile(
      promptOverlayPath,
      [
        'import { ok } from "openclaw/plugin-sdk/provider-model-shared";',
        "export { ok };",
        '',
      ].join('\n'),
    );

    const result = patchExtensionOpenClawSelfImports(root);

    expect(result).toMatchObject({
      filesPatched: 1,
      specifiersPatched: 1,
    });
    await expect(readFile(promptOverlayPath, 'utf8')).resolves.toContain(
      'from "../../plugin-sdk/provider-model-shared.js"',
    );
  });

  it('leaves extension files without OpenClaw self-imports untouched', async () => {
    const root = await createTempOpenClawBundle();
    const extensionDir = path.join(root, 'dist', 'extensions', 'telegram');
    await mkdir(extensionDir, { recursive: true });

    const filePath = path.join(extensionDir, 'runtime.js');
    const source = 'export const runtime = true;\n';
    await writeFile(filePath, source);

    const result = patchExtensionOpenClawSelfImports(root);

    expect(result.filesScanned).toBe(1);
    expect(result.filesPatched).toBe(0);
    expect(result.specifiersPatched).toBe(0);
    await expect(readFile(filePath, 'utf8')).resolves.toBe(source);
  });

  it('throws when the bundled plugin-sdk target is missing', () => {
    const root = path.join(tmpdir(), 'missing-target-openclaw-bundle');
    const distDir = path.join(root, 'dist');
    const filePath = path.join(distDir, 'extensions', 'codex', 'prompt-overlay.js');

    expect(() => rewriteOpenClawPluginSdkSpecifiers(
      'import "openclaw/plugin-sdk/provider-model-shared";',
      { filePath, distDir },
    )).toThrow(/missing bundled SDK target/);
  });

  it('formats same-directory import paths with an explicit relative prefix', () => {
    expect(toImportSpecifier('provider-model-shared.js')).toBe('./provider-model-shared.js');
    expect(toImportSpecifier('../plugin-sdk/provider-model-shared.js')).toBe(
      '../plugin-sdk/provider-model-shared.js',
    );
  });

  it('returns an empty patch summary when the extensions directory is absent', async () => {
    const root = await createTempOpenClawBundle();
    await mkdir(path.join(root, 'dist', 'plugin-sdk'), { recursive: true });

    const result = patchExtensionOpenClawSelfImports(root);

    expect(existsSync(path.join(root, 'dist', 'extensions'))).toBe(false);
    expect(result).toEqual({
      filesScanned: 0,
      filesPatched: 0,
      specifiersPatched: 0,
    });
  });
});