/**
 * refresh-clinic-names — 从摄入源派生展示名字典写入 host:
 *   - `clinic_directory` → host.clinicNames  (诊所 id → 名)
 *   - `brand_directory`  → host.sourceUnitNames (品牌/source_unit id → 名;多品牌宿主用)
 *
 * /auth/session 会把 host.clinicNames 合并进 dictionary.clinics 下发,
 * 前端显示诊所名不再依赖宿主换票时传 dictionary(见 Host.clinicNames 注释)。
 *
 * 源在 manifest 的 `clinic_directory` / `brand_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 覆盖>]
 *
 * 幂等:整表 upsert host.clinicNames(全量覆盖为最新派生结果)。可接 cron 定期刷。
 */
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;
  const bd = manifest.brand_directory;
  if (!cd && !bd) {
    logger.error(`manifest 未声明 clinic_directory / brand_directory —— 无可派生的名字典`);
    process.exit(1);
  }

  // 两类字典同机制派生(声明了才跑);sql_source 走 CH,文件源读 tables[]
  const derive = async (d: { table: string; id_field: string; name_field: string }) =>
    manifest.sql_source
      ? await deriveFromClickhouse(manifest.sql_source, d, logger)
      : deriveFromFile(manifestDir, manifest.tables ?? [], d);

  const clinicMap = cd ? await derive(cd) : {};
  const brandMap = bd ? await derive(bd) : {};
  const nc = Object.keys(clinicMap).length;
  const nb = Object.keys(brandMap).length;
  logger.log(`派生:诊所名 ${nc} 家 / 品牌名 ${nb} 个`);
  if (nc === 0 && nb === 0) {
    logger.warn('派生结果全空,跳过写入(不清空既有字典)');
    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}" 不存在`);
    // 空结果不覆盖既有(单侧声明时不误清另一侧)
    await prisma.host.update({
      where: { id: host.id },
      data: {
        ...(nc > 0 ? { clinicNames: clinicMap } : {}),
        ...(nb > 0 ? { sourceUnitNames: brandMap } : {}),
      },
    });
    if (nc > 0)
      logger.log(`✓ host="${hostName}" clinicNames ${nc} 家(样例: ${Object.values(clinicMap).slice(0, 3).join(' / ')})`);
    if (nb > 0)
      logger.log(`✓ host="${hostName}" sourceUnitNames ${nb} 个(样例: ${Object.values(brandMap).slice(0, 3).join(' / ')})`);
  } finally {
    await app.close();
  }
}

void main();
