import { execFileSync } from 'node:child_process';
import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';

/**
 * deploy-prod.sh 的「机器形态自检」回归 —— 锁住 2026-07-26 生产事故的防线。
 *
 * 事故:在 friday 生产机漏了 `COMPOSE_MANAGED=1` 跑 deploy-prod.sh。不叠加 managed override 时,
 *   docker-compose.prod.yml 把 DATABASE_URL 硬编码成 `postgres:5432`(容器内自带 PG),于是:
 *     · 当场起了 pac-postgres-1 / pac-redis-1 两个空容器
 *     · 29 条迁移建到那个**空库**上(托管库本身没被写坏,已逐项核对数据完好)
 *   真正的后遗症是机器上从此留了个「schema 齐全的空库」—— 下次再漏 COMPOSE_MANAGED,
 *   service 会顺利连上它、health 200、脚本三项验证全绿,**生产静默服务空库且零告警**。
 *   原本"空 PG 起不来会崩"这张天然安全网就此失效。
 *
 * 防线口径:不依赖人记得加环境变量,而是问机器自己 —— 读 `.env` 的 DATABASE_URL 指向哪儿。
 *   指向 postgres/localhost/127.0.0.1 → 自带 DB 形态,放行
 *   指向别的主机(RDS 等)            → 托管形态,必须 COMPOSE_MANAGED=1,否则 die
 */

const SCRIPT = resolve(__dirname, '../../../deploy/deploy-prod.sh');

/** 造一棵最小目录树(deploy/ + 两个 .env),跑脚本并回收 stdout+stderr。 */
function runGuard(dbUrl: string, env: NodeJS.ProcessEnv): { code: number; out: string } {
  const dir = mkdtempSync(join(tmpdir(), 'pac-deploy-guard-'));
  try {
    mkdirSync(join(dir, 'deploy'), { recursive: true });
    mkdirSync(join(dir, 'apps/pac-service'), { recursive: true });
    mkdirSync(join(dir, 'apps/pac-web'), { recursive: true });
    cpSync(SCRIPT, join(dir, 'deploy/deploy-prod.sh'));
    writeFileSync(join(dir, 'apps/pac-service/.env'), `DATABASE_URL="${dbUrl}"\n`);
    writeFileSync(join(dir, 'apps/pac-web/.env'), '');
    try {
      const out = execFileSync('bash', ['deploy/deploy-prod.sh', '--no-pull'], {
        cwd: dir,
        // git/docker 在临时目录里必然失败 —— 无所谓,闸在它们之前,只看闸放不放行
        env: { ...process.env, ...env },
        encoding: 'utf8',
        stdio: ['ignore', 'pipe', 'pipe'],
        timeout: 30_000,
      });
      return { code: 0, out };
    } catch (e) {
      const err = e as { status?: number; stdout?: string; stderr?: string };
      return { code: err.status ?? -1, out: `${err.stdout ?? ''}${err.stderr ?? ''}` };
    }
  } finally {
    rmSync(dir, { recursive: true, force: true });
  }
}

const RDS = 'postgresql://pac:s3cr3t@fridaypac.pg.rds.aliyuncs.com:5432/pac?schema=public';
const BUNDLED = 'postgresql://pac:pac@postgres:5432/pac?schema=public';

describe('deploy-prod.sh 机器形态自检(COMPOSE_MANAGED 防线)', () => {
  test('⭐ 托管 DB + 漏设 COMPOSE_MANAGED → 必须退出,且给出正确命令', () => {
    const { code, out } = runGuard(RDS, { COMPOSE_MANAGED: '' });
    expect(code).not.toBe(0);
    expect(out).toContain('托管 DB 形态');
    expect(out).toContain('COMPOSE_MANAGED=1 bash deploy/deploy-prod.sh');
  });

  test('⭐ 报错信息只暴露主机名,不能把 .env 里的口令打出来', () => {
    const { out } = runGuard(RDS, { COMPOSE_MANAGED: '' });
    expect(out).toContain('fridaypac.pg.rds.aliyuncs.com');
    expect(out).not.toContain('s3cr3t');
  });

  test('⭐ 托管 DB + COMPOSE_MANAGED=1 → 放行(闸不拦,后面失败是因为临时目录没 git/docker)', () => {
    const { out } = runGuard(RDS, { COMPOSE_MANAGED: '1' });
    expect(out).not.toContain('托管 DB 形态');
  });

  test('自带 DB(postgres:5432)+ 不设 COMPOSE_MANAGED → 放行(测试机形态,不能误伤)', () => {
    const { out } = runGuard(BUNDLED, { COMPOSE_MANAGED: '' });
    expect(out).not.toContain('托管 DB 形态');
  });

  test('localhost / 127.0.0.1 同样按自带 DB 放行', () => {
    for (const host of ['localhost', '127.0.0.1']) {
      const { out } = runGuard(`postgresql://pac:pac@${host}:5432/pac`, { COMPOSE_MANAGED: '' });
      expect(out).not.toContain('托管 DB 形态');
    }
  });
});
