import { Injectable, Logger } from '@nestjs/common';
import {
  Prisma,
  RobotLifecycleStage,
  RobotLifecycleEventType,
  RobotPhysicalStatus,
  RobotWarrantyStatus,
} from '@prisma/client';

/**
 * 事件 → Snapshot 投影器
 *
 * 每条 LifecycleEvent 创建后，按事件类型刷新对应 Snapshot 字段。
 * 同事务执行（prisma.$transaction）保证一致性。
 *
 * 投影 map 来源 最终字段设计.md §5.5
 */
@Injectable()
export class SnapshotProjectorService {
  private readonly logger = new Logger(SnapshotProjectorService.name);

  /**
   * 事件触发后刷新 Snapshot
   * @param tx Prisma 事务 client
   * @param event 已创建的 LifecycleEvent
   * @param expectedVersion 乐观锁版本（防并发）；undefined 表示新建 Snapshot
   */
  async project(
    tx: Prisma.TransactionClient,
    event: {
      id: string;
      robotUnitId: string;
      eventType: RobotLifecycleEventType;
      fromStage: RobotLifecycleStage | null;
      toStage: RobotLifecycleStage | null;
      fromLocationId: string | null;
      toLocationId: string | null;
      customerId: string | null;
      relatedType: string | null;
      relatedId: string | null;
      payload: any;
      occurredAt: Date;
    },
    expectedVersion?: number,
  ): Promise<void> {
    const fields: Prisma.RobotUnitSnapshotUncheckedUpdateInput = {
      lastEventId: event.id,
      lastEventAt: event.occurredAt,
      derivedAt: new Date(),
      version: expectedVersion !== undefined ? expectedVersion + 1 : 1,
    };

    switch (event.eventType) {
      case RobotLifecycleEventType.stage_changed:
        if (event.toStage) fields.currentStage = event.toStage;
        // 进入 DELIVERY_DELIVERED 同步刷新 warrantyStatus + physicalStatus
        if (event.toStage === RobotLifecycleStage.DELIVERY_DELIVERED) {
          fields.warrantyStatus = RobotWarrantyStatus.ACTIVE;
          fields.physicalProductStatus = RobotPhysicalStatus.DELIVERED;
        }
        if (event.toStage === RobotLifecycleStage.CLOSED) {
          fields.physicalProductStatus = RobotPhysicalStatus.RETIRED;
        }
        if (event.toStage === RobotLifecycleStage.AFTERSALES_UNDER_REPAIR) {
          fields.physicalProductStatus = RobotPhysicalStatus.IN_SERVICE;
        }
        // 进入 SALES_RESERVED：从绑定的 SalesOrderLine 同步刷 currentSalesOrderId + currentCustomerId
        // 之前漏刷导致 robot 详情/列表 join "客户/订单" 时缺失关联（E2E bug #3）
        if (event.toStage === RobotLifecycleStage.SALES_RESERVED) {
          const line = await tx.salesOrderLine.findFirst({
            where: { robotUnitId: event.robotUnitId },
            select: { salesOrderId: true, salesOrder: { select: { customerId: true } } },
          });
          if (line) {
            fields.currentSalesOrderId = line.salesOrderId;
            fields.currentCustomerId = line.salesOrder.customerId;
          }
        }
        break;

      case RobotLifecycleEventType.held:
        fields.isHeld = true;
        fields.holdReason = event.payload?.holdReason ?? null;
        break;

      case RobotLifecycleEventType.unheld:
        fields.isHeld = false;
        fields.holdReason = null;
        break;

      case RobotLifecycleEventType.location_moved:
        if (event.toLocationId) fields.currentLocationId = event.toLocationId;
        break;

      case RobotLifecycleEventType.readiness_completed:
        if (event.payload?.specialistId) {
          fields.currentSpecialistId = event.payload.specialistId;
        }
        if (event.payload?.physicalProductStatus) {
          fields.physicalProductStatus = event.payload.physicalProductStatus;
        }
        if (event.payload?.readyAt) {
          const ready = new Date(event.payload.readyAt);
          fields.daysReadyForDelivery = Math.floor(
            (Date.now() - ready.getTime()) / 86400_000,
          );
        }
        break;

      case RobotLifecycleEventType.delivery_signed:
        if (event.customerId) fields.currentCustomerId = event.customerId;
        if (event.relatedType === 'DELIVERY_REQUEST' && event.relatedId) {
          fields.currentDeliveryRequestId = event.relatedId;
        }
        if (event.payload?.salesOrderId) {
          fields.currentSalesOrderId = event.payload.salesOrderId;
        }
        fields.warrantyStatus = RobotWarrantyStatus.ACTIVE;
        fields.physicalProductStatus = RobotPhysicalStatus.DELIVERED;
        break;

      // 不影响 snapshot 的事件类型
      case RobotLifecycleEventType.sn_activated:
      case RobotLifecycleEventType.usage_type_changed:
      case RobotLifecycleEventType.label_applied:
      case RobotLifecycleEventType.inspection_logged:
      case RobotLifecycleEventType.payment_collected:
      case RobotLifecycleEventType.service_opened:
      case RobotLifecycleEventType.service_closed:
      case RobotLifecycleEventType.note_added:
        // 仅写 event 流，不刷 snapshot 业务字段（但 lastEventId/lastEventAt 仍刷）
        break;

      case RobotLifecycleEventType.imported_from_v5: {
        // v5 历史导入：白名单字段重建 snapshot（防止 payload 注入 PK / FK / version）
        const ALLOWED_IMPORT_KEYS = new Set([
          'currentStage',
          'isHeld',
          'holdReason',
          'currentLocationId',
          'currentCustomerId',
          'currentSalesOrderId',
          'currentDeliveryRequestId',
          'currentSpecialistId',
          'physicalProductStatus',
          'daysReadyForDelivery',
          'warrantyStatus',
        ]);
        const snap = event.payload?.snapshot ?? {};
        for (const key of Object.keys(snap)) {
          if (ALLOWED_IMPORT_KEYS.has(key)) {
            (fields as Record<string, unknown>)[key] = snap[key];
          }
        }
        break;
      }
    }

    // 乐观锁 update（version=expectedVersion）；若新建则 upsert
    if (expectedVersion !== undefined) {
      const result = await tx.robotUnitSnapshot.updateMany({
        where: { robotUnitId: event.robotUnitId, version: expectedVersion },
        data: fields,
      });
      if (result.count === 0) {
        throw new Error(
          `Snapshot version conflict for unit ${event.robotUnitId} (expected version ${expectedVersion})`,
        );
      }
    } else {
      // 新建 snapshot（首次 stage_changed）— spread 在前，PK/必填 在后保证不被覆盖
      const createData: Prisma.RobotUnitSnapshotUncheckedCreateInput = {
        ...(fields as Prisma.RobotUnitSnapshotUncheckedCreateInput),
        robotUnitId: event.robotUnitId,
        currentStage: event.toStage ?? RobotLifecycleStage.SUPPLY_PO_CREATED,
      };
      await tx.robotUnitSnapshot.create({ data: createData });
    }
  }
}
