import { describe, expect, it } from 'vitest';
import {
  classifyScientificArtifact,
  collectScientificBrowserDiagnostics,
  diffRuntimeFiles,
  RuntimeWorkerManager,
  type RuntimeExecutionRequest,
  type RuntimeWorkspacePort,
  type ScientificRuntimeSession,
} from './index';

class MemoryRuntimeWorkspace implements RuntimeWorkspacePort {
  readonly files = new Map<string, Uint8Array>();

  async reset(): Promise<void> {
    this.files.clear();
  }

  async ensureDirectory(): Promise<void> {}

  async writeFile(path: string, data: Uint8Array): Promise<void> {
    this.files.set(path, data.slice());
  }

  async readFile(path: string): Promise<Uint8Array | undefined> {
    return this.files.get(path)?.slice();
  }

  async listFiles() {
    return [...this.files].map(([path, data]) => ({ path, size: data.byteLength }));
  }
}

function fakePythonSession(): ScientificRuntimeSession {
  const workspace = new MemoryRuntimeWorkspace();
  return {
    id: 'python',
    label: 'Fake Python',
    capabilities: { packages: true, plots: true, workspaceFiles: true, interrupt: true },
    workspace,
    async initialize() {},
    async execute(_request: RuntimeExecutionRequest) {
      return {
        runtime: 'python',
        exitCode: 0,
        stdout: 'ok\n',
        stderr: '',
        startedAt: 1,
        durationMs: 2,
      };
    },
    async interrupt() {},
    async dispose() {},
  };
}

describe('RuntimeWorkerManager', () => {
  it('lazily creates and reuses one runtime per workspace', async () => {
    const manager = new RuntimeWorkerManager();
    let created = 0;
    manager.register({
      id: 'python',
      label: 'Python',
      create: () => {
        created += 1;
        return fakePythonSession();
      },
    });

    const first = await manager.ensureReady('paper', 'python');
    const second = await manager.ensureReady('paper', 'python');
    expect(first).toBe(second);
    expect(created).toBe(1);
    expect(manager.getState('paper', 'python')?.status).toBe('ready');
  });

  it('does not invent unregistered runtimes', async () => {
    const manager = new RuntimeWorkerManager();
    await expect(manager.ensureReady('paper', 'r')).rejects.toThrow(/not registered/u);
  });


  it('evicts least-recently-used idle sessions instead of retaining heavyweight runtimes for every workspace', async () => {
    const manager = new RuntimeWorkerManager(1);
    let disposed = 0;
    manager.register({
      id: 'python',
      label: 'Python',
      create: () => {
        const session = fakePythonSession();
        return { ...session, async dispose() { disposed += 1; } };
      },
    });

    await manager.ensureReady('paper-a', 'python');
    await manager.ensureReady('paper-b', 'python');
    expect(manager.getState('paper-a', 'python')).toBeUndefined();
    expect(manager.getState('paper-b', 'python')?.status).toBe('ready');
    expect(disposed).toBe(1);
  });
});

describe('runtime file deltas', () => {
  it('separates created, modified and deleted files', () => {
    const delta = diffRuntimeFiles(
      [
        { path: 'same.csv', size: 1, hash: 'a' },
        { path: 'changed.csv', size: 1, hash: 'a' },
        { path: 'gone.csv', size: 1, hash: 'a' },
      ],
      [
        { path: 'same.csv', size: 1, hash: 'a' },
        { path: 'changed.csv', size: 1, hash: 'b' },
        { path: 'new.csv', size: 1, hash: 'c' },
      ],
    );
    expect(delta.created.map((item) => item.path)).toEqual(['new.csv']);
    expect(delta.modified.map((item) => item.path)).toEqual(['changed.csv']);
    expect(delta.deleted.map((item) => item.path)).toEqual(['gone.csv']);
  });
});

describe('scientific artifact classification', () => {
  it('recognises figure and dataset outputs', () => {
    expect(classifyScientificArtifact('figures/model.pdf')).toBe('plot');
    expect(classifyScientificArtifact('results/summary.csv')).toBe('dataset');
    expect(classifyScientificArtifact('reports/index.html')).toBe('report');
  });
});


describe('scientific browser diagnostics', () => {
  it('marks Python degraded rather than unsupported when only fine-grained interruption is unavailable', () => {
    const report = collectScientificBrowserDiagnostics({
      webAssembly: true,
      worker: true,
      fetch: true,
      abortController: true,
      cryptoRandomUUID: true,
      sharedArrayBuffer: false,
      atomics: true,
      crossOriginIsolated: false,
      offscreenCanvas: true,
      domCanvas: true,
    });
    expect(report.runtimes.find((runtime) => runtime.runtimeId === 'python')?.status).toBe('degraded');
    expect(report.runtimes.find((runtime) => runtime.runtimeId === 'r')?.status).toBe('supported');
    expect(report.runtimes.find((runtime) => runtime.runtimeId === 'wasi')?.status).toBe('unsupported');
  });

  it('marks runtimes unsupported when secure random UUID support is unavailable', () => {
    const report = collectScientificBrowserDiagnostics({
      webAssembly: true,
      worker: true,
      fetch: true,
      abortController: true,
      cryptoRandomUUID: false,
      sharedArrayBuffer: true,
      atomics: true,
      crossOriginIsolated: true,
      offscreenCanvas: true,
      domCanvas: true,
    });
    expect(report.runtimes.every((runtime) => runtime.status === 'unsupported')).toBe(true);
  });

  it('marks all runtimes unsupported when WebAssembly is unavailable', () => {
    const report = collectScientificBrowserDiagnostics({
      webAssembly: false,
      worker: true,
      fetch: true,
      abortController: true,
      cryptoRandomUUID: true,
      sharedArrayBuffer: true,
      atomics: true,
      crossOriginIsolated: true,
      offscreenCanvas: true,
      domCanvas: true,
    });
    expect(report.runtimes.every((runtime) => runtime.status === 'unsupported')).toBe(true);
  });
});
