import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import {
  classifyPopulation,
  type Skill,
  type SkillMatchContext,
} from './skill.types';
import type { DraftPlanScriptInput } from './input.types';
import { resolveScriptSkillsRoot } from './skill-registry.service';

/**
 * SkillComposer — 纯函数式,把 input + 全 skills → matched skills + system prompt。
 *
 * 流程:
 *   1. 从 input 派生 SkillMatchContext(scenario / diagnosisCodes / age / population / relationship)
 *   2. 全 skills 过滤 applies match
 *   3. 跨维度排除(如 child + K08 → drop K08;allowedPopulation 列表非空时必须包含当前 population)
 *   4. 按 priority 升序排
 *   5. 拼装:base-system.md + matched skills body
 *   6. 算 composeHash(给 promptVersion 用,版本归因 SQL `GROUP BY promptVersion` 看效果)
 *
 * Composer 不持有状态,所有 input 走参数 — 易测、可 inline 在 call.ts 跑。
 */

export interface ComposedSystem {
  /** 拼好的完整 system prompt(base + 各 skill body) */
  systemPrompt: string;
  /** 匹配到的 skills(已按 priority 升序);测试 + agent_invocations 落账 + user prompt 末尾清单都用 */
  matchedSkills: Skill[];
  /** match context(给 user prompt build 用,避免重算) */
  context: SkillMatchContext;
  /** composeHash 给 promptVersion suffix 用,16 hex */
  composeHash: string;
}

/** base-system.md 路径(跟 registry 共用 cwd-based 多路径 resolver) */
function resolveBaseSystemPath(): string {
  return join(resolveScriptSkillsRoot(), 'base-system.md');
}

/** lazy load base system,只读 1 次缓存 */
let cachedBase: string | null = null;
function loadBaseSystem(): string {
  if (cachedBase !== null) return cachedBase;
  const raw = readFileSync(resolveBaseSystemPath(), 'utf-8').trim();
  cachedBase = raw;
  return raw;
}

/**
 * 从 input 派生 match context — orchestrator 已在 reason 上直传 scenario raw key + subKey + dxCode,
 * composer 这里不做文本推断,纯字段映射。
 */
export function deriveContext(input: DraftPlanScriptInput): SkillMatchContext {
  const reasons = input.plan.reasons ?? [];
  const scenario = input.plan.primaryScenarioKey ?? null;
  const diagnosisCodes = Array.from(
    new Set(reasons.map((r) => r.dxCode).filter((c): c is string => !!c)),
  );
  const age = input.patient.age ?? null;
  return {
    scenario,
    diagnosisCodes,
    age,
    population: classifyPopulation(age),
    relationship: input.clinicalContext.completedTreatmentCount === 0 ? 'new' : 'returning',
  };
}

/** 一个 skill 是否匹配当前 context */
export function skillApplies(skill: Skill, ctx: SkillMatchContext): boolean {
  const a = skill.frontmatter.applies;
  if (a.scenario && a.scenario !== ctx.scenario) return false;
  if (a.diagnosisCodePrefix) {
    const hit = ctx.diagnosisCodes.some((c) =>
      c.startsWith(a.diagnosisCodePrefix!),
    );
    if (!hit) return false;
  }
  // 年龄未知(null):按业务规格"未知 → 默认成人模板"——通过 ageMin(成人/成人以上),
  //   但仍被 ageMax 排除(child 的 ageMax 命中 null → 不匹配),所以 null 只落成人。
  if (a.ageMin !== undefined && ctx.age !== null && ctx.age < a.ageMin) return false;
  if (a.ageMax !== undefined && (ctx.age === null || ctx.age > a.ageMax)) return false;
  if (a.relationship && a.relationship !== ctx.relationship) return false;
  // allowedPopulation 跨维度排除:非空数组时,当前 population 必须在列表内
  if (
    skill.frontmatter.allowedPopulation.length > 0 &&
    (ctx.population === null ||
      !skill.frontmatter.allowedPopulation.includes(ctx.population))
  ) {
    return false;
  }
  return true;
}

/**
 * Compose 主入口。
 */
export function composeSystem(
  input: DraftPlanScriptInput,
  allSkills: readonly Skill[],
): ComposedSystem {
  const context = deriveContext(input);
  const matched = allSkills
    .filter((s) => skillApplies(s, context))
    .sort(
      (a, b) =>
        (a.frontmatter.priority ?? 50) - (b.frontmatter.priority ?? 50),
    );

  const base = loadBaseSystem();
  // 只拼 body — 内部 skill name/version 不进提示词(版本归因走 composeHash,见下)
  const skillsBlock = matched.map((s) => s.body).join('\n\n---\n\n');

  const systemPrompt = skillsBlock
    ? `${base}\n\n# 本次话术模板\n\n${skillsBlock}`
    : base;

  // composeHash = sha256(matched.name+version join)前 16 hex
  const hashSrc = matched
    .map((s) => `${s.frontmatter.name}@${s.frontmatter.version}`)
    .join('|');
  const composeHash = createHash('sha256').update(hashSrc).digest('hex').slice(0, 16);

  return { systemPrompt, matchedSkills: matched, context, composeHash };
}
