/**
 * Sync Channel Test CLI — 验证 Pull / Reconcile / Push 端到端走通(无需真宿主)。
 *
 * 前置:
 *   1. 一个 host 行,name='mock-host'(或任意 demo 命名),pullConfig={"adapter":"mock"}
 *   2. push 测试:同 host 行,pushSecretHashes=['dev-secret-123'](数组,raw secret)
 *
 * 用法:
 *   pnpm sync:test pull-setup            # 一键 seed 一个 mock-host(幂等)
 *   pnpm sync:test pull --host=mock-host        # 触发一次 pull(走 cursor)
 *   pnpm sync:test pull --host=mock-host        # 再触发,cursor 推进,无新数据
 *   pnpm sync:test reconcile --host=mock-host --days=7  # 时间窗对账(全 dup 是正常)
 *   pnpm sync:test push --host=mock-host                # 发一条 push 事件(自动签名)
 *
 * 输出每次 SyncLog id + metrics 便于 DB 验证。
 */
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from '../app.module';
import { PrismaService } from '../prisma/prisma.service';
import { PullOrchestrator } from '../modules/sync/pull/pull.orchestrator';
import { ReconcileOrchestrator } from '../modules/sync/reconcile/reconcile.orchestrator';
import { HmacVerifier } from '../modules/sync/push/hmac-verifier.service';
import { randomUUID, createHash } from 'node:crypto';

interface CliArgs {
  cmd: 'pull-setup' | 'pull' | 'reconcile' | 'push' | 'help';
  host: string;
  days: number;
  apiBase: string;
}

function parseArgs(argv: string[]): CliArgs {
  const out: CliArgs = {
    cmd: (argv[0] as CliArgs['cmd']) ?? 'help',
    host: 'mock-host',
    days: 7,
    apiBase: process.env.PAC_API_BASE_URL ?? 'http://localhost:3101',
  };
  for (const a of argv.slice(1)) {
    if (a.startsWith('--host=')) out.host = a.slice('--host='.length);
    else if (a.startsWith('--days=')) out.days = Number(a.slice('--days='.length));
    else if (a.startsWith('--api=')) out.apiBase = a.slice('--api='.length);
  }
  return out;
}

function printHelp() {
  // eslint-disable-next-line no-console
  console.log(`
Sync Channel Test CLI

Commands:
  pull-setup        seed 一个 mock-host(若不存在)
  pull              触发 PullOrchestrator(cursor 续传)
  reconcile         触发 ReconcileOrchestrator(时间窗对账)
  push              本进程直接调 PushReceiver(模拟宿主 webhook)

Options:
  --host=<name>     host name(default mock-host)
  --days=<n>        reconcile 时间窗(default 7)
  --api=<url>       (push 模式用)PAC api base url(default http://localhost:3101)
`);
}

async function bootstrap() {
  const args = parseArgs(process.argv.slice(2));
  if (args.cmd === 'help' || !['pull-setup', 'pull', 'reconcile', 'push'].includes(args.cmd)) {
    printHelp();
    process.exit(0);
  }
  const logger = new Logger('sync:test');
  const app = await NestFactory.createApplicationContext(AppModule, { logger: ['warn', 'error'] });

  try {
    const prisma = app.get(PrismaService);

    if (args.cmd === 'pull-setup') {
      const existing = await prisma.host.findUnique({ where: { name: args.host } });
      if (existing) {
        logger.log(`host=${args.host} 已存在 id=${existing.id}`);
        return;
      }
      const created = await prisma.host.create({
        data: {
          name: args.host,
          appId: `${args.host}-app`,
          appSecretHash: scryptStubHash('mock-secret'),
          pullConfig: {
            adapter: 'mock',
            mock: { totalPatients: 4, pageSize: 2, withAppointments: true },
            amountUnit: 'fen',
            timezone: 'Asia/Shanghai',
          },
          pushSecretHashes: ['dev-secret-123'],
          actionUrls: {},
          active: true,
        },
      });
      logger.log(`✓ created host=${args.host} id=${created.id}`);
      logger.log(`  pullConfig.adapter=mock | pushSecret='dev-secret-123'`);
      return;
    }

    const host = await prisma.host.findUnique({ where: { name: args.host } });
    if (!host) {
      logger.error(`host=${args.host} 不存在,先跑 pull-setup`);
      process.exit(2);
    }

    if (args.cmd === 'pull') {
      const orch = app.get(PullOrchestrator);
      const result = await orch.runForHost(host.id, { triggeredBy: 'manual:cli' });
      console.log(JSON.stringify(result, null, 2));
    } else if (args.cmd === 'reconcile') {
      const orch = app.get(ReconcileOrchestrator);
      const result = await orch.runForHost(host.id, {
        windowDays: args.days,
        triggeredBy: `reconcile:manual:${new Date().toISOString().slice(0, 10)}`,
      });
      console.log(JSON.stringify(result, null, 2));
    } else if (args.cmd === 'push') {
      await testPush(args.apiBase, host.id, 'dev-secret-123');
    }
  } catch (err) {
    logger.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
    process.exitCode = 2;
  } finally {
    await app.close();
  }
}

async function testPush(apiBase: string, hostId: string, secret: string) {
  // 合成一条 push 事件(1 个 patient + 1 个 appointment)
  const now = new Date();
  const events = [
    {
      subjectType: 'patient',
      action: 'patient_created',
      subjectId: `push-p${randomUUID().slice(0, 6)}`,
      tenantId: 't_demo',
      sourceUnit: '',
      clinicId: 'mock-clinic-1',
      occurredAt: now.toISOString(),
      updatedAt: now.toISOString(),
      payload: {
        name: '推送测试-张三',
        phone: '13900001111',
        gender: 'M',
        birthDate: '1985-06-15',
      },
    },
  ];
  const body = JSON.stringify({ events });
  const ts = Math.floor(Date.now() / 1000);
  const sig = HmacVerifier.sign(secret, ts, body);

  const url = `${apiBase}/pac/v1/push/events`;
  // eslint-disable-next-line no-console
  console.log(`POST ${url}\n  host=${hostId} ts=${ts} sig=${sig.slice(0, 16)}...\n  body=${body.slice(0, 200)}`);

  const res = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-PAC-Host-Id': hostId,
      'X-PAC-Timestamp': String(ts),
      'X-PAC-Signature': sig,
    },
    body,
  });
  const text = await res.text();
  // eslint-disable-next-line no-console
  console.log(`← HTTP ${res.status}\n${text}`);
}

function scryptStubHash(secret: string): string {
  // dev 用 sha256 占位;真鉴权走 host-admin CLI 设置 scrypt
  return `dev:${createHash('sha256').update(secret).digest('hex')}`;
}

bootstrap().catch((err) => {
  // eslint-disable-next-line no-console
  console.error(err);
  process.exit(3);
});
