import { Injectable, Logger } from '@nestjs/common';
import { createHash } from 'node:crypto';
import { FactStatus } from '@pac/types';
import type { Prisma } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import type { FactDraft } from './parsers/parser.interface';
import {
  FactContentSchemaError,
  validateFactContent,
} from './fact-content-schemas';

/**
 * FactWriter — patient_facts 版本流写入器
 *
 * 输入:一条 FactDraft + 触发它的 transactionId + (hostId, tenantId, patientId)
 *
 * 行为(对单 subject_id):
 *   1. 找当前 active 版本(partial UNIQUE 保证唯一)
 *   2. 计算 draft.content 的稳定 hash;
 *      ┌─ 跟 active 一致 → 幂等(把当前 transactionId 追加到 active.transactionIds,如未存在)
 *      └─ 不一致 / 无 active → supersede 旧版本 → 插入新版本 version = old.version + 1(无则 1)
 *
 * 并发说明:
 *   - W2 是单进程顺序写,不需要锁
 *   - W3+ 上 BullMQ 时按 subject_id 串行化(jobName 对齐)+ 事务内 SELECT FOR UPDATE
 *   - schema 层 partial UNIQUE active 兜底
 *
 * 返回:WriteResult — 用于上层 metrics 统计(facts_created / facts_superseded / facts_unchanged)
 */
@Injectable()
export class FactWriter {
  private readonly logger = new Logger(FactWriter.name);

  constructor(private readonly prisma: PrismaService) {}

  async writeDraft(input: {
    draft: FactDraft;
    hostId: string;
    tenantId: string;
    patientId: string;
    transactionId: string;
  }): Promise<FactWriteResult> {
    const { draft, hostId, tenantId, patientId, transactionId } = input;

    // ─── 闸 1:写库前 zod 强校验(canonical-fact-layer.md §二)──
    // 失败抛 FactContentSchemaError,由 ParserPipeline 计入 factsFailed 并 log issues。
    // 不允许"宽松落库"— 脏数据进 fact 层会污染整个下游(scenario / persona / AI)。
    let validatedContent: Prisma.InputJsonValue;
    try {
      validatedContent = validateFactContent(
        draft.type,
        draft.subjectId,
        draft.content,
      ) as Prisma.InputJsonValue;
    } catch (err) {
      if (err instanceof FactContentSchemaError) {
        this.logger.error(
          `[schema-violation] type=${err.factType} subjectId=${err.subjectId} ` +
            `transactionId=${transactionId} issues=${JSON.stringify(err.issues)}`,
        );
      }
      throw err;
    }

    const validatedDraft: FactDraft = { ...draft, content: validatedContent };
    const newHash = this.hashContent(validatedDraft.content);

    // 用一次 $transaction 把"读最新版本 → 决策 → supersede / insert"原子化。
    //
    // ⚠️ 必须查最新版本(不能只查 active)— 因为 fulfilled / cancelled / expired
    //    等终态版本"没有 active",但 (subjectId, version) UNIQUE 约束仍在 —
    //    要算下一 version 必须知道历史最高 version。
    // ⭐ 默认 timeout 5s 在 cold-import 大压力 / swap thrashing 下不够,撑到 60s
    //   fact-writer 是最热路径,每 fact 一次 tx,失败一次 = 整条 patient pipeline 中断
    return this.prisma.$transaction(async (tx) => {
      const latest = await tx.patientFact.findFirst({
        where: {
          hostId,
          tenantId,
          subjectId: draft.subjectId,
        },
        orderBy: { version: 'desc' },
      });

      const draftStatus = draft.status ?? FactStatus.ACTIVE;

      if (latest) {
        const latestHash = this.hashContent(latest.content as Prisma.InputJsonValue);
        const sameContent = latestHash === newHash;
        const sameStatus = latest.status === draftStatus;

        if (sameContent && sameStatus) {
          // 完全一致 — 幂等。把当前 transaction 加进证据数组(去重)
          if (!latest.transactionIds.includes(transactionId)) {
            await tx.patientFact.update({
              where: { id: latest.id },
              data: { transactionIds: [...latest.transactionIds, transactionId] },
            });
            return {
              action: 'evidence_appended',
              factId: latest.id,
              subjectId: draft.subjectId,
              version: latest.version,
            };
          }
          return {
            action: 'unchanged',
            factId: latest.id,
            subjectId: draft.subjectId,
            version: latest.version,
          };
        }

        // 有变化(content 或 status)— 如果 latest 是 active,supersede 它;
        // 终态版本(cancelled/fulfilled/expired/invalidated/superseded)不动,只追新版本。
        if (latest.status === FactStatus.ACTIVE) {
          await tx.patientFact.update({
            where: { id: latest.id },
            data: {
              status: FactStatus.SUPERSEDED,
              supersededAt: new Date(),
            },
          });
        }
      }

      const nextVersion = (latest?.version ?? 0) + 1;
      const created = await tx.patientFact.create({
        data: {
          hostId,
          tenantId,
          patientId,
          subjectId: validatedDraft.subjectId,
          kind: validatedDraft.kind,
          type: validatedDraft.type,
          status: validatedDraft.status ?? FactStatus.ACTIVE,
          version: nextVersion,
          clinicId: validatedDraft.clinicId ?? null,
          occurredAt: validatedDraft.occurredAt ?? null,
          plannedFor: validatedDraft.plannedFor ?? null,
          validFrom: validatedDraft.validFrom ?? null,
          validUntil: validatedDraft.validUntil ?? null,
          title: validatedDraft.title ?? null,
          summary: validatedDraft.summary ?? null,
          content: validatedDraft.content,
          transactionIds: [transactionId],
        },
      });

      return {
        action: latest ? 'superseded' : 'created',
        factId: created.id,
        subjectId: draft.subjectId,
        version: nextVersion,
      };
    }, { maxWait: 30000, timeout: 60000 });
  }

  /**
   * 稳定 JSON hash — 递归按 key 排序后 sha256。
   * JSON.stringify 默认按 insertion order,key 顺序不同会算出不同 hash。
   */
  private hashContent(content: Prisma.InputJsonValue): string {
    return createHash('sha256').update(this.canonicalize(content)).digest('hex');
  }

  private canonicalize(v: unknown): string {
    if (v === null || v === undefined) return 'null';
    if (typeof v === 'number' || typeof v === 'boolean') return JSON.stringify(v);
    if (typeof v === 'string') return JSON.stringify(v);
    if (Array.isArray(v)) {
      return '[' + v.map((x) => this.canonicalize(x)).join(',') + ']';
    }
    if (typeof v === 'object') {
      const obj = v as Record<string, unknown>;
      const keys = Object.keys(obj).sort();
      return (
        '{' +
        keys
          .map((k) => JSON.stringify(k) + ':' + this.canonicalize(obj[k]))
          .join(',') +
        '}'
      );
    }
    return JSON.stringify(v);
  }
}

export interface FactWriteResult {
  /// created    — 新增首个版本(无前态)
  /// superseded — 旧 active 被置为 superseded,新版本插入
  /// evidence_appended — 内容未变,但本次 transaction 被加进证据数组
  /// unchanged  — 内容未变且 transaction 已在证据数组中(完全幂等)
  action: 'created' | 'superseded' | 'evidence_appended' | 'unchanged';
  factId: string;
  subjectId: string;
  version: number;
}
