import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, writeFileSync, mkdirSync, rmSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { getStage, listStages, loadCustomStages, clearCustomStages } from '../src/proxymodel/stage-registry.js';

let tempDir: string;

beforeEach(() => {
  tempDir = mkdtempSync(join(tmpdir(), 'mcpctl-hotreload-'));
  clearCustomStages();
});

afterEach(() => {
  clearCustomStages();
  rmSync(tempDir, { recursive: true, force: true });
});

describe('Hot-reload: stage registry cache busting', () => {
  it('loadCustomStages loads .js files', async () => {
    writeFileSync(join(tempDir, 'echo.js'), `
      export default async function(content, ctx) {
        return { content: 'v1:' + content };
      }
    `);

    await loadCustomStages(tempDir);

    const handler = getStage('echo');
    expect(handler).not.toBeNull();
    const result = await handler!('hello', {} as Parameters<typeof handler>[1]);
    expect(result.content).toBe('v1:hello');
  });

  it('reloading picks up file changes via cache busting', async () => {
    writeFileSync(join(tempDir, 'transform.js'), `
      export default async function(content) {
        return { content: 'v1:' + content };
      }
    `);

    await loadCustomStages(tempDir);
    let handler = getStage('transform');
    let result = await handler!('test', {} as Parameters<typeof handler>[1]);
    expect(result.content).toBe('v1:test');

    // Overwrite the file with a new version
    writeFileSync(join(tempDir, 'transform.js'), `
      export default async function(content) {
        return { content: 'v2:' + content };
      }
    `);

    // Reload — should pick up the new version due to cache busting
    await loadCustomStages(tempDir);
    handler = getStage('transform');
    result = await handler!('test', {} as Parameters<typeof handler>[1]);
    expect(result.content).toBe('v2:test');
  });

  it('removing a file removes the stage on reload', async () => {
    writeFileSync(join(tempDir, 'temp.js'), `
      export default async function(content) {
        return { content };
      }
    `);

    await loadCustomStages(tempDir);
    expect(getStage('temp')).not.toBeNull();

    unlinkSync(join(tempDir, 'temp.js'));
    await loadCustomStages(tempDir);
    expect(getStage('temp')).toBeNull();
  });

  it('adding a new file makes it available on reload', async () => {
    await loadCustomStages(tempDir);
    expect(getStage('newstage')).toBeNull();

    writeFileSync(join(tempDir, 'newstage.js'), `
      export default async function(content) {
        return { content: 'new:' + content };
      }
    `);

    await loadCustomStages(tempDir);
    const handler = getStage('newstage');
    expect(handler).not.toBeNull();
    const result = await handler!('x', {} as Parameters<typeof handler>[1]);
    expect(result.content).toBe('new:x');
  });

  it('syntax errors in stage files do not crash reload', async () => {
    writeFileSync(join(tempDir, 'good.js'), `
      export default async function(content) {
        return { content };
      }
    `);
    writeFileSync(join(tempDir, 'bad.js'), 'this is not valid javascript{{{');

    await loadCustomStages(tempDir);
    // Good stage should still load
    expect(getStage('good')).not.toBeNull();
    // Bad stage should not be present
    expect(getStage('bad')).toBeNull();
  });

  it('supports .mjs files', async () => {
    writeFileSync(join(tempDir, 'mjs-stage.mjs'), `
      export default async function(content) {
        return { content: 'mjs:' + content };
      }
    `);

    await loadCustomStages(tempDir);
    const handler = getStage('mjs-stage');
    expect(handler).not.toBeNull();
    const result = await handler!('hi', {} as Parameters<typeof handler>[1]);
    expect(result.content).toBe('mjs:hi');
  });

  it('listStages shows custom stages as local', async () => {
    writeFileSync(join(tempDir, 'custom.js'), `
      export default async function(content) {
        return { content };
      }
    `);

    await loadCustomStages(tempDir);
    const stages = listStages();
    const custom = stages.find((s) => s.name === 'custom');
    expect(custom).toBeDefined();
    expect(custom!.source).toBe('local');
  });
});
