import { Injectable, Logger } from '@nestjs/common';
import { createHmac } from 'node:crypto';
import { PrismaService } from '../../prisma/prisma.service';

/**
 * ExecutionCallbackService — 执行结果回执投递(PAC → 宿主 callbackUrl)。
 *
 * 契约见 apps/pac-docs/content/docs/integration/execution-callback.mdx:
 *   POST {callbackUrl}
 *   Headers: X-PAC-Timestamp / X-PAC-Signature / X-PAC-Event-Id
 *   签名: hex(HMAC-SHA256("{timestamp}.{rawBody}", callbackSecret))  — 与 push 入站同一套配方
 *
 * 由 execution-callback 队列的 worker 调用;失败抛错交 BullMQ 退避重试(耗尽入死信)。
 */

const DELIVER_TIMEOUT_MS = 8_000;

/** 投递结果:worker 据 delivered 决定成功 / 抛错重试。 */
export interface DeliverResult {
  delivered: boolean;
  /** 跳过原因(未启用回执 / execution 不存在等 —— 非失败,不重试) */
  skipped?: string;
  httpStatus?: number;
}

@Injectable()
export class ExecutionCallbackService {
  private readonly logger = new Logger(ExecutionCallbackService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * 投递单条 execution 的回执。
   *   - 宿主未配 callbackUrl / 未配密钥 → skipped(opt-in,不算失败)
   *   - HTTP 2xx → delivered
   *   - 非 2xx / 超时 / 网络错 → 抛错(worker 交 BullMQ 重试)
   */
  async deliver(executionId: string): Promise<DeliverResult> {
    const exec = await this.prisma.planExecution.findUnique({ where: { id: executionId } });
    if (!exec) return { delivered: false, skipped: 'execution 不存在' };

    const plan = await this.prisma.followupPlan.findUnique({
      where: { id: exec.planId },
      select: { status: true, patientId: true, hostId: true, tenantId: true },
    });
    if (!plan) return { delivered: false, skipped: 'plan 不存在' };

    const host = await this.prisma.host.findUnique({
      where: { id: plan.hostId },
      select: { callbackUrl: true, callbackSecrets: true },
    });
    if (!host?.callbackUrl) return { delivered: false, skipped: '未配置 callbackUrl' };
    const secret = host.callbackSecrets[0];
    if (!secret) return { delivered: false, skipped: '未配置 callback secret' };

    const patient = plan.patientId
      ? await this.prisma.patient.findUnique({
          where: { id: plan.patientId },
          select: { externalId: true },
        })
      : null;

    // 请求体(字段对齐对接文档;可选字段无值时省略)
    const body: Record<string, unknown> = {
      eventId: exec.id,
      patientExternalId: patient?.externalId ?? null,
      operatorUserId: exec.operatorUserId,
      channel: exec.channel,
      outcome: exec.outcome,
      planStatus: plan.status,
      occurredAt: exec.createdAt.toISOString(),
    };
    if (exec.notes) body.notes = exec.notes;
    if (exec.scheduledNextAt) body.scheduledNextAt = exec.scheduledNextAt.toISOString();

    const rawBody = JSON.stringify(body);
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const signature = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');

    let res: Response;
    try {
      res = await fetch(host.callbackUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-PAC-Timestamp': timestamp,
          'X-PAC-Signature': signature,
          'X-PAC-Event-Id': exec.id,
        },
        body: rawBody,
        signal: AbortSignal.timeout(DELIVER_TIMEOUT_MS),
      });
    } catch (err) {
      // 超时 / 网络错 → 抛错重试
      throw new Error(`回执投递网络失败 execution=${executionId}: ${(err as Error).message}`);
    }

    if (res.status < 200 || res.status >= 300) {
      throw new Error(`回执投递被拒 execution=${executionId} http=${res.status}`);
    }

    this.logger.log(
      `回执已投递 execution=${executionId.slice(0, 8)} outcome=${exec.outcome} http=${res.status}`,
    );
    return { delivered: true, httpStatus: res.status };
  }
}
