/**
 * refresh-clinic-names — 从摄入源派生「诊所 id → 名字」写入 host.clinicNames。
 *
 * /auth/session 会把 host.clinicNames 合并进 dictionary.clinics 下发,
 * 前端显示诊所名不再依赖宿主换票时传 dictionary(见 Host.clinicNames 注释)。
 *
 * 源在 manifest 的 `clinic_directory: { table, id_field, name_field }` 声明:
 *   - sql_source 模式:SELECT DISTINCT id,name FROM <table>(连 DW,env 覆盖连接)
 *   - 文件模式:读 tables[] 里对应 <table> 的 CSV/JSON,去重
 *
 * 用法:
 *   pnpm refresh-clinic-names -- --dir=./data/jvs-dw [--host=<name 覆盖>]
 *
 * 幂等:派生结果**合并**进 host.clinicNames(同 key 新值胜,旧 key 保留)。可接 cron 定期刷。
 *   合并而非覆盖:文件模式的导出可能是 cohort 子集(export.sh --clinics/--since),
 *   覆盖会把子集外诊所的名字刷掉。
 */
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
import { parse as parseCsv } from 'csv-parse/sync';
import { createClient } from '@clickhouse/client';
import { AppModule } from '../app.module';
import { PrismaService } from '../prisma/prisma.service';
import { ColdImportManifestSchema } from '../modules/sync/cold-import/manifest.schema';

// CLI 是短命进程,不需要 org-tree 启动预热(且会在 app.close() 时跟后台 warmAll 抢连接报噪音)。
process.env.PAC_ORGTREE_WARM_ON_BOOT = 'false';

function flag(name: string): string | undefined {
  const hit = process.argv.slice(2).find((a) => a.startsWith(`--${name}=`));
  return hit?.slice(name.length + 3);
}

async function deriveFromClickhouse(
  source: NonNullable<ReturnType<typeof ColdImportManifestSchema.parse>['sql_source']>,
  dir: { table: string; id_field: string; name_field: string },
  logger: Logger,
): Promise<Record<string, string>> {
  const url = process.env.DW_CLICKHOUSE_URL?.trim() || source.connection.url;
  const database = process.env.DW_CLICKHOUSE_DATABASE?.trim() || source.connection.database;
  const username = process.env.DW_CLICKHOUSE_USERNAME?.trim() || source.connection.username;
  const password = process.env[source.connection.password_env];
  if (!password) throw new Error(`ClickHouse 密码缺失:环境变量 ${source.connection.password_env} 未设`);
  const client = createClient({ url, database, username, password, request_timeout: 60_000 });
  try {
    // 表名可能是 queries 的 key(逻辑名)→ 取其真实 FROM 表;否则当真实表名
    const realTable = (() => {
      const sql = source.queries[dir.table];
      const m = sql?.match(/FROM\s+([\w.]+)/i);
      return m ? m[1] : dir.table;
    })();
    const q =
      `SELECT ${dir.id_field} AS id, any(${dir.name_field}) AS name ` +
      `FROM ${realTable} WHERE notEmpty(${dir.id_field}) AND notEmpty(${dir.name_field}) ` +
      `GROUP BY ${dir.id_field}`;
    logger.log(`CH 派生: ${q}`);
    const rs = await client.query({ query: q, format: 'JSONEachRow' });
    const rows = (await rs.json()) as Array<{ id: string; name: string }>;
    const map: Record<string, string> = {};
    for (const r of rows) if (r.id && r.name) map[String(r.id)] = String(r.name);
    return map;
  } finally {
    await client.close();
  }
}

function deriveFromFile(
  manifestDir: string,
  tables: Array<{ table: string; file: string; format?: 'csv' | 'json' }>,
  dir: { table: string; id_field: string; name_field: string },
): Record<string, string> {
  const t = tables.find((x) => x.table === dir.table);
  if (!t) throw new Error(`directory.table=${dir.table} 不在 tables[] 里`);
  const p = path.resolve(manifestDir, t.file);
  const raw = fs.readFileSync(p, 'utf-8');
  const rows: Array<Record<string, unknown>> = t.file.toLowerCase().endsWith('.json')
    ? JSON.parse(raw)
    : parseCsv(raw, { columns: true, skip_empty_lines: true, trim: true });
  const map: Record<string, string> = {};
  for (const r of rows) {
    const id = r[dir.id_field];
    const name = r[dir.name_field];
    if (id != null && String(id) !== '' && name != null && String(name) !== '') {
      map[String(id)] = String(name);
    }
  }
  return map;
}

async function main(): Promise<void> {
  const logger = new Logger('refresh-clinic-names');
  const dirArg = flag('dir');
  if (!dirArg) {
    logger.error('用法: pnpm refresh-clinic-names -- --dir=./data/<host> [--host=<name>]');
    process.exit(1);
  }
  const manifestDir = path.resolve(process.cwd(), dirArg);
  const manifest = ColdImportManifestSchema.parse(
    yaml.load(fs.readFileSync(path.join(manifestDir, 'manifest.yaml'), 'utf-8')),
  );
  const hostName = flag('host') || manifest.host_name;
  const cd = manifest.clinic_directory;
  if (!cd) {
    logger.error(`manifest 未声明 clinic_directory —— 无法派生诊所名`);
    process.exit(1);
  }

  const clinicMap = manifest.sql_source
    ? await deriveFromClickhouse(manifest.sql_source, cd, logger)
    : deriveFromFile(manifestDir, manifest.tables ?? [], cd);
  const nc = Object.keys(clinicMap).length;
  logger.log(`派生诊所名 ${nc} 家`);
  if (nc === 0) {
    logger.warn('派生结果为空,跳过写入(不清空既有 clinicNames)');
    process.exit(0);
  }

  const app = await NestFactory.createApplicationContext(AppModule, { logger: ['warn', 'error'] });
  try {
    const prisma = app.get(PrismaService);
    const host = await prisma.host.findFirst({ where: { name: hostName } });
    if (!host) throw new Error(`host "${hostName}" 不存在`);
    // 合并而非整表覆盖:导出文件可能是 cohort 子集(export.sh --clinics/--since),
    // 覆盖会把子集外诊所的名字刷掉。同 key 新值胜(改名照常生效),旧 key 保留。
    const merged = { ...((host.clinicNames as Record<string, string>) ?? {}), ...clinicMap };
    await prisma.host.update({ where: { id: host.id }, data: { clinicNames: merged } });
    logger.log(
      `✓ host="${hostName}" clinicNames 本次派生 ${nc} 家,合并后 ${Object.keys(merged).length} 家` +
        `(样例: ${Object.values(clinicMap).slice(0, 3).join(' / ')})`,
    );
  } finally {
    await app.close();
  }
}

void main();
