import { afterEach, describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as os from 'node:os';
import * as path from 'node:path';
import { runMxCli, parseLastJson, assertExitCode } from '../helpers/cli-harness.js';

function addressPort(server: http.Server): number {
  const address = server.address();
  if (!address || typeof address === 'string') {
    throw new Error('Fixture registry did not bind a TCP port');
  }
  return address.port;
}

function asRecord(value: unknown): Record<string, unknown> {
  assert.ok(value !== null && typeof value === 'object' && !Array.isArray(value));
  return value as Record<string, unknown>;
}

async function startRegistryFixture(): Promise<{ readonly registryUrl: string; readonly close: () => Promise<void> }> {
  const server = http.createServer((req, res) => {
    const registryUrl = `http://127.0.0.1:${addressPort(server)}/api/packages/open-matrix/npm/`;
    const url = new URL(req.url ?? '/', registryUrl);
    if (req.method === 'GET' && url.pathname.endsWith('/-/whoami')) {
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({ username: 'alice' }));
      return;
    }
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(JSON.stringify({ ok: true }));
  });

  await new Promise<void>((resolve, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => {
      server.off('error', reject);
      resolve();
    });
  });

  return {
    registryUrl: `http://127.0.0.1:${addressPort(server)}/api/packages/open-matrix/npm/`,
    close: async () => {
      await new Promise<void>((resolve, reject) => {
        server.close((error) => (error ? reject(error) : resolve()));
      });
    },
  };
}

describe('FP-30 mx registry commands', { concurrency: false }, () => {
  const tempDirs: string[] = [];
  const restoreEnvFns: Array<() => void> = [];
  const fixtureClosers: Array<() => Promise<void>> = [];

  afterEach(async () => {
    while (fixtureClosers.length > 0) {
      const close = fixtureClosers.pop();
      if (close) {
        await close();
      }
    }
    while (restoreEnvFns.length > 0) {
      const restore = restoreEnvFns.pop();
      if (restore) {
        restore();
      }
    }
    while (tempDirs.length > 0) {
      const dir = tempDirs.pop();
      if (dir) {
        fs.rmSync(dir, { recursive: true, force: true });
      }
    }
  });

  function useCleanEnv(root: string): void {
    const previousHome = process.env.HOME;
    const previousMatrixHome = process.env.MATRIX_HOME;
    const previousCliConfig = process.env.MATRIX_CLI_CONFIG;
    const previousPackageRegistry = process.env.MATRIX_PACKAGE_REGISTRY;
    const previousRegistryUrl = process.env.MATRIX_REGISTRY_URL;
    const previousNpmUserConfig = process.env.NPM_CONFIG_USERCONFIG;
    const previousNodeAuthToken = process.env.NODE_AUTH_TOKEN;
    const previousNpmToken = process.env.NPM_TOKEN;
    const home = path.join(root, 'home');
    fs.mkdirSync(home, { recursive: true });
    process.env.HOME = home;
    process.env.MATRIX_HOME = path.join(root, '.matrix');
    delete process.env.MATRIX_CLI_CONFIG;
    delete process.env.MATRIX_PACKAGE_REGISTRY;
    delete process.env.MATRIX_REGISTRY_URL;
    delete process.env.NPM_CONFIG_USERCONFIG;
    delete process.env.NODE_AUTH_TOKEN;
    delete process.env.NPM_TOKEN;
    restoreEnvFns.push(() => {
      restoreEnv('HOME', previousHome);
      restoreEnv('MATRIX_HOME', previousMatrixHome);
      restoreEnv('MATRIX_CLI_CONFIG', previousCliConfig);
      restoreEnv('MATRIX_PACKAGE_REGISTRY', previousPackageRegistry);
      restoreEnv('MATRIX_REGISTRY_URL', previousRegistryUrl);
      restoreEnv('NPM_CONFIG_USERCONFIG', previousNpmUserConfig);
      restoreEnv('NODE_AUTH_TOKEN', previousNodeAuthToken);
      restoreEnv('NPM_TOKEN', previousNpmToken);
    });
  }

  function restoreEnv(key: string, value: string | undefined): void {
    if (value === undefined) {
      delete process.env[key];
      return;
    }
    process.env[key] = value;
  }

  it('uses a local Gitea registry profile and diagnoses scope config without reading poisoned user npmrc', async () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-registry-command-'));
    tempDirs.push(root);
    useCleanEnv(root);
    fs.writeFileSync(path.join(process.env.HOME!, '.npmrc'), [
      '@matrix:registry=https://wrong.example.invalid/',
      '@open-matrix:registry=https://wrong.example.invalid/',
      '@omega:registry=https://wrong.example.invalid/',
      '',
    ].join('\n'), 'utf8');
    const fixture = await startRegistryFixture();
    fixtureClosers.push(fixture.close);
    const credentialsFile = path.join(root, 'credentials.json');

    const useResult = await runMxCli([
      'node',
      'mx',
      'registry',
      'use',
      'local-gitea',
      '--registry',
      fixture.registryUrl,
      '--json',
    ], { cwd: root });
    assertExitCode(useResult, 0);
    assert.equal((parseLastJson(useResult.logs) as Record<string, unknown>).registry, fixture.registryUrl);

    const loginResult = await runMxCli([
      'node',
      'mx',
      'registry',
      'login',
      '--registry',
      fixture.registryUrl,
      '--username',
      'alice',
      '--token',
      'token-registry-command',
      '--credentials-file',
      credentialsFile,
      '--json',
    ], { cwd: root });
    assertExitCode(loginResult, 0);

    const doctorResult = await runMxCli([
      'node',
      'mx',
      'registry',
      'doctor',
      '--registry',
      fixture.registryUrl,
      '--credentials-file',
      credentialsFile,
      '--timeout-ms',
      '5000',
      '--json',
    ], { cwd: root });
    assertExitCode(doctorResult, 0);
    const doctor = asRecord(parseLastJson(doctorResult.logs));
    const registry = asRecord(doctor.registry);
    const credentials = asRecord(doctor.credentials);
    const npm = asRecord(doctor.npm);
    assert.equal(doctor.ok, true);
    assert.equal(registry.url, fixture.registryUrl);
    assert.equal(credentials.found, true);
    assert.equal(credentials.authOk, true);
    assert.equal(npm.allScopesMatch, true);
    assert.equal(npm.poisonedUserConfigIgnored, true);
    assert.deepEqual(npm.scopeResolution, {
      '@open-matrix': fixture.registryUrl,
      '@matrix': fixture.registryUrl,
      '@omega': fixture.registryUrl,
    });
  });
});
