import { Injectable, Logger } from '@nestjs/common';
import { AiCallRunnerService } from '../../../../ai-call-runner.service';
import { InvocationRecorderService } from '../../../../core/invocation-recorder.service';
import type { AiCallContext } from '../../../../ai-call.interface';
import type { ScriptContext } from '../../shared/input.types';
import { machineSafetyScan } from '../../shared/safety-rules';
import { stableTemplateFallback } from '../stable/stable.call';
import { DeepPlanCall, DeepWriteCall, DeepVerifyCall } from './calls';
import { draftOutputToDeep, type DeepDraft, type DeepPlan, type DeepVerify, type DeepVerifyIssue } from './types';

export interface DeepScriptResult {
  draft: DeepDraft;
  source: 'agent' | 'template_fallback';
  invocationId: string;
  cacheHit: boolean;
  costYuan: number;
  promptTokens: number;
  completionTokens: number;
  fallbackReason?: string;
  stepsRun: string[];
}

/**
 * DeepScriptStrategy —— 深度档 3 步编排(脊柱仍是 AiCallRunner,每步各落 agent_invocations)。
 *   规划(plan) → 写(write,多段) → 独立对抗校验(verify) → 不过则 repair(≤1 轮) → 仍不过则稳健兜底。
 * 机器安全(禁词/承诺/加粗时间)在策略侧硬扫(machineSafetyScan),与 LLM 校验合并决定是否 repair。
 */
@Injectable()
export class DeepScriptStrategy {
  private readonly logger = new Logger(DeepScriptStrategy.name);
  private readonly MAX_REPAIR = 1;

  constructor(
    private readonly runner: AiCallRunnerService,
    private readonly recorder: InvocationRecorderService,
    private readonly planCall: DeepPlanCall,
    private readonly writeCall: DeepWriteCall,
    private readonly verifyCall: DeepVerifyCall,
  ) {}

  async run(ctx: ScriptContext, runCtx: AiCallContext): Promise<DeepScriptResult> {
    const steps: string[] = [];
    let cost = 0;
    let promptTokens = 0;
    let completionTokens = 0;
    let allCacheHit = true; // P1-2:各步全命中缓存才算 cacheHit(别再硬编码 false)
    let ranAny = false;
    const acc = (r: { costYuan: number; promptTokens: number; completionTokens: number; cacheHit: boolean }) => {
      cost += r.costYuan;
      promptTokens += r.promptTokens;
      completionTokens += r.completionTokens;
      allCacheHit = allCacheHit && r.cacheHit;
      ranAny = true;
    };
    // 客户端断连 → 各步之间快速短路(在途调用由 runner 的 abortSignal 取消)
    const ensureLive = () => {
      if (runCtx.signal?.aborted) throw new Error('生成已取消(客户端断连)');
    };

    // ── 步骤1:规划(best-effort;失败用退化大纲,不阻断) ──
    ensureLive();
    let plan: DeepPlan;
    try {
      const r = await this.runner.run(this.planCall, ctx, runCtx);
      acc(r);
      plan = r.output;
      steps.push('plan');
    } catch (err) {
      if (runCtx.signal?.aborted) throw err; // abort 不退化,直接抛
      this.logger.warn(`deep plan 失败,用退化大纲: ${(err as Error).message}`);
      plan = degeneratePlan();
      steps.push('plan:degenerate');
    }

    // ── 步骤2:写(有 fallback,必返回) ──
    ensureLive();
    let w = await this.runner.run(this.writeCall, { ctx, plan }, runCtx);
    acc(w);
    let draft = w.output;
    let invocationId = w.invocationId;
    steps.push(w.source === 'template_fallback' ? 'write:fallback' : 'write');
    if (w.source === 'template_fallback') {
      return this.done(draft, 'template_fallback', invocationId, cost, promptTokens, completionTokens, steps, false, w.fallbackReason);
    }

    // ── 步骤3:独立对抗校验 + 机器扫 ──
    const issues: DeepVerifyIssue[] = machineScanIssues(draft);
    let quality: DeepVerify['quality'] | null = null; // 质量分:回填留到最后,挂到 PlanScript 指向的最终 invocation
    ensureLive();
    try {
      const v = await this.runner.run(this.verifyCall, { ctx, draft }, runCtx);
      acc(v);
      steps.push('verify');
      if (!v.output.pass) issues.push(...v.output.issues);
      quality = v.output.quality ?? null;
    } catch (err) {
      if (runCtx.signal?.aborted) throw err;
      this.logger.warn(`deep verify 失败,仅依据机器扫: ${(err as Error).message}`);
      steps.push('verify:skip');
    }

    // ── repair(≤1 轮) ──
    if (issues.length > 0) {
      ensureLive();
      this.logger.debug(`deep repair: ${issues.length} 个 issue`);
      const w2 = await this.runner.run(this.writeCall, { ctx, plan, repairIssues: issues }, runCtx);
      acc(w2);
      draft = w2.output;
      invocationId = w2.invocationId;
      steps.push(w2.source === 'template_fallback' ? 'repair:fallback' : 'repair');
      // 终检:机器闸仍不过 → 稳健兜底(对抗哲学:接地/安全宁兜底不放行)
      const stillBad = machineSafetyScan(joinDraft(draft));
      if (w2.source === 'template_fallback' || stillBad.length > 0) {
        const fb = draftOutputToDeep(stableTemplateFallback(ctx));
        return this.done(fb, 'template_fallback', invocationId, cost, promptTokens, completionTokens, steps, false, `repair 后仍不过: ${stillBad.join(';')}`);
      }
    }

    // P1-1:质量分回填到【最终】invocationId(= PlanScript 指向的那条),非 gate,失败忽略。
    //   注:若 repair 过,quality 是 repair 前那稿的评分,挂到 repair 稿上属近似(repair 只修违规、质量相近)。
    if (quality) {
      await this.recorder
        .attachJudge(invocationId, quality.overall, {
          natural: quality.natural,
          warmth: quality.warmth,
          focus: quality.focus,
          nonPushy: quality.nonPushy,
        })
        .catch((e) => this.logger.warn(`attachJudge 失败(忽略): ${(e as Error).message}`));
    }

    return this.done(draft, 'agent', invocationId, cost, promptTokens, completionTokens, steps, ranAny && allCacheHit);
  }

  private done(
    draft: DeepDraft,
    source: 'agent' | 'template_fallback',
    invocationId: string,
    costYuan: number,
    promptTokens: number,
    completionTokens: number,
    stepsRun: string[],
    cacheHit: boolean,
    fallbackReason?: string,
  ): DeepScriptResult {
    this.logger.log(`deep done source=${source} steps=[${stepsRun.join('→')}] cost=${costYuan.toFixed(4)}`);
    return { draft, source, invocationId, cacheHit, costYuan, promptTokens, completionTokens, fallbackReason, stepsRun };
  }
}

/** 把整篇草稿拼成纯文本(机器安全扫用) */
function joinDraft(draft: DeepDraft): string {
  return draft.sections.map((s) => s.markdown).join('\n');
}

/** 机器扫 → issue 列表(回喂 writer repair) */
function machineScanIssues(draft: DeepDraft): DeepVerifyIssue[] {
  return machineSafetyScan(joinDraft(draft)).map((p) => ({
    section: '(机器安全闸)',
    problem: p,
    fix: '删除该表述 / 价格时间改成不承诺、用【时间段】占位',
  }));
}

/** 规划失败时的退化大纲(4 段;写步仍能产出) */
function degeneratePlan(): DeepPlan {
  return {
    sections: [
      {
        key: 'opening',
        title: '开场问候',
        intent: '打招呼 + 自报家门 + 医生关怀 + 问近况',
        points: ['用患者称呼打招呼、确认方便', '自报家门', '以诊断医生名义体现关怀', '用最近一次就诊问近况'],
      },
      {
        key: 'missed',
        title: '切入本次问题',
        intent: '温和带出本次问题',
        points: ['结合病历讲清不处理的隐患与趁早处理的好处'],
      },
      {
        key: 'review',
        title: '复查邀约',
        intent: '邀约来院复查本次问题',
        points: ['用固定预约句式 + 【时间段】占位引导预约'],
      },
      {
        key: 'close',
        title: '结束',
        intent: '简短收尾',
        points: ['约成功确认时间道别;没约成温和留口子,时间用【具体预约时间】占位'],
      },
    ],
  };
}
