import { describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { actorCommand } from '../../src/commands/actor.js';
import { installOpenMatrixCoreStub } from '../support/installCoreStub.ts';
import { loadMatrixServiceManifest } from '../../src/utils/service-manifest.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, '../../../../');

describe('mx actor command', () => {
  it('creates a compilable actor scaffold package', async () => {
    const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-actor-'));
    const outputDir = path.join(tmpRoot, 'my-weather-service');

    try {
      const result = await actorCommand('my-weather-service', {
        cwd: tmpRoot,
        outputDir,
        dev: true,
      });

      assert.equal(result.rootDir, outputDir);
      assert.equal(result.workspacePath, path.join(tmpRoot, '.matrix', 'workspace.json'));
      assert.equal(fs.existsSync(path.join(outputDir, 'matrix.json')), true);
      assert.equal(fs.existsSync(path.join(outputDir, 'src', `${result.className}.ts`)), true);
      assert.equal(fs.existsSync(path.join(outputDir, 'src', 'index.ts')), true);
      assert.equal(fs.existsSync(path.join(outputDir, 'examples', 'get-status.mxq')), true);
      assert.equal(fs.existsSync(path.join(outputDir, 'skills', 'my-weather-service.skill.md')), true);
      assert.equal(fs.existsSync(path.join(outputDir, 'tests', 'my-weather-service.spec.ts')), true);
      assert.equal(fs.existsSync(path.join(outputDir, 'package.json')), true, 'package.json should be generated');
      assert.equal(fs.existsSync(path.join(outputDir, 'tsconfig.json')), true, 'tsconfig.json should be generated');
      assert.equal(fs.existsSync(path.join(outputDir, 'matrix.service.json')), false, 'standalone factory is declared in matrix.json');
      assert.equal(
        fs.existsSync(path.join(outputDir, 'src', `create-standalone-${result.className}.ts`)),
        true,
        'standalone service factory should be generated',
      );

      const pkg = JSON.parse(fs.readFileSync(path.join(outputDir, 'package.json'), 'utf8'));
      assert.equal(pkg.type, 'module', 'package.json should have type: module');
      assert.ok(Array.isArray(pkg.files), 'package.json should have files array');
      assert.equal(pkg.files.includes('matrix.service.json'), false, 'package files should not ship a second service manifest');
      assert.equal(pkg.scripts?.build, 'tsc -p tsconfig.json');
      assert.equal(pkg.scripts?.['build:watch'], 'tsc -w -p tsconfig.json --preserveWatchOutput');
      assert.equal(pkg.scripts?.dev, 'tsc -w -p tsconfig.json --preserveWatchOutput');
      assert.equal(pkg.scripts?.test, 'node --import tsx --test tests/**/*.spec.ts');
      assert.equal(pkg.dependencies?.['@open-matrix/core'], '*', 'actor service package should declare runtime SDK dependency');
      assert.equal(pkg.devDependencies?.['@open-matrix/core'], undefined, 'actor service package should not hide runtime SDK in devDependencies');

      const tsconfig = JSON.parse(fs.readFileSync(path.join(outputDir, 'tsconfig.json'), 'utf8'));
      assert.ok(tsconfig.compilerOptions, 'tsconfig.json should have compilerOptions');
      assert.equal(tsconfig.compilerOptions.target, 'ES2022');
      assert.equal(tsconfig.compilerOptions.incremental, true);
      assert.equal(tsconfig.compilerOptions.tsBuildInfoFile, '.tsbuildinfo');
      assert.equal(tsconfig.compilerOptions.sourceMap, true);

      const manifest = JSON.parse(fs.readFileSync(path.join(outputDir, 'matrix.json'), 'utf8')) as {
        name: string;
        runtime?: {
          factory?: {
            kind?: string;
            export?: string;
            instance?: {
              rootKind?: string;
              componentType?: string;
              autoStart?: boolean;
            };
          };
        };
        components: Array<{ mount?: string; type?: string }>;
      };
      assert.equal(manifest.name, '@matrix/my-weather-service');
      assert.equal(manifest.components[0]?.mount, 'my-weather-service');
      assert.equal(manifest.components[0]?.type, result.className);
      assert.equal(manifest.runtime?.factory?.kind, 'factory');
      assert.equal(manifest.runtime?.factory?.export, `createStandalone${result.className}`);
      assert.equal(manifest.runtime?.factory?.instance?.rootKind, 'component');
      assert.equal(manifest.runtime?.factory?.instance?.componentType, result.className);
      assert.equal(manifest.runtime?.factory?.instance?.autoStart, true);

      // Install a minimal runtime stub so the scaffolded actor can resolve
      // the published package import shape without depending on a built workspace copy.
      installOpenMatrixCoreStub(outputDir);

      const compile = spawnSync(
        process.execPath,
        [
          path.resolve(repoRoot, 'node_modules/tsx/dist/cli.mjs'),
          path.join(outputDir, 'src', `${result.className}.ts`),
        ],
        { cwd: outputDir, encoding: 'utf8' }
      );
      assert.equal(
        compile.status,
        0,
        `expected generated actor to compile, stderr: ${compile.stderr || '(none)'}`
      );

      const build = spawnSync(
        process.execPath,
        [
          path.resolve(repoRoot, 'node_modules/typescript/bin/tsc'),
          '-p',
          path.join(outputDir, 'tsconfig.json'),
        ],
        { cwd: outputDir, encoding: 'utf8' },
      );
      assert.equal(build.status, 0, `expected generated package to build, stderr: ${build.stderr || '(none)'}`);

      const loadedService = loadMatrixServiceManifest(outputDir);
      assert.equal(loadedService.manifestPath, path.join(outputDir, 'matrix.json'));
      assert.equal(loadedService.exportName, `createStandalone${result.className}`);
      assert.equal(loadedService.entryPath, path.join(outputDir, 'dist', 'index.js'));
      assert.equal(loadedService.serviceInstance.rootKind, 'component');
      assert.equal(loadedService.serviceInstance.mount, 'my-weather-service');

      const workspace = JSON.parse(fs.readFileSync(result.workspacePath!, 'utf8'));
      assert.equal(workspace.home, '.matrix/home');
      assert.deepEqual(workspace.default, ['system', 'host-control', 'my-weather-service']);
      assert.deepEqual(workspace.services?.system, {
        package: '@open-matrix/system',
        store: 'system',
      });
      assert.deepEqual(workspace.services?.['host-control'], {
        package: '@open-matrix/host-control',
        store: 'system',
      });
      assert.deepEqual(workspace.services?.['my-weather-service'], {
        path: './my-weather-service',
      });
    } finally {
      fs.rmSync(tmpRoot, { recursive: true, force: true });
    }
  });

  it('adds the source checkout system service before the actor in dev workspaces', async () => {
    const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-actor-system-dev-'));
    const systemDir = path.join(tmpRoot, 'projects', 'matrix-3', 'packages', 'system');
    const hostControlDir = path.join(tmpRoot, 'projects', 'matrix-3', 'packages', 'host-control');

    try {
      fs.mkdirSync(systemDir, { recursive: true });
      fs.writeFileSync(path.join(systemDir, 'package.json'), JSON.stringify({ name: '@open-matrix/system' }), 'utf8');
      fs.writeFileSync(path.join(systemDir, 'matrix.json'), JSON.stringify({ name: '@open-matrix/system' }), 'utf8');
      fs.mkdirSync(hostControlDir, { recursive: true });
      fs.writeFileSync(path.join(hostControlDir, 'package.json'), JSON.stringify({ name: '@open-matrix/host-control' }), 'utf8');
      fs.writeFileSync(path.join(hostControlDir, 'matrix.json'), JSON.stringify({ name: '@open-matrix/host-control' }), 'utf8');

      const result = await actorCommand('dev-loop-demo', {
        cwd: tmpRoot,
        outputDir: path.join(tmpRoot, 'dev-loop-demo'),
        dev: true,
      });

      const workspace = JSON.parse(fs.readFileSync(result.workspacePath!, 'utf8'));
      assert.equal(workspace.home, '.matrix/home');
      assert.deepEqual(workspace.default, ['system', 'host-control', 'dev-loop-demo']);
      assert.deepEqual(workspace.services?.system, { path: './projects/matrix-3/packages/system' });
      assert.deepEqual(workspace.services?.['host-control'], { path: './projects/matrix-3/packages/host-control' });
      assert.deepEqual(workspace.services?.['dev-loop-demo'], { path: './dev-loop-demo' });
    } finally {
      fs.rmSync(tmpRoot, { recursive: true, force: true });
    }
  });
});
