import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import {
  Prisma,
  PurchaseOrderStatus,
  RobotLifecycleEventType,
  RobotLifecycleStage,
  RobotUsageType,
} from '@prisma/client';
import { SkipAssertAccess } from '@common/decorators/skip-assert-access.decorator';
import { SnapshotProjectorService } from '../services/snapshot-projector.service';

export interface CreatePurchaseOrderInput {
  poNo: string;
  supplierId: string;
  currencyCode: string;
  orderedAt: Date | string;
  expectedAt?: Date | string;
  notes?: string;
  lines: Array<{
    lineNo: number;
    skuId: string;
    quantity: number;
    unitPrice: number;
    currencyCode: string;
    defaultUsageType?: RobotUsageType;
    placeholderPattern?: string;
    expectedAt?: Date | string;
  }>;
}

export interface UpdatePurchaseOrderInput {
  status?: PurchaseOrderStatus;
  expectedAt?: Date | string;
  closedAt?: Date | string;
  notes?: string;
  sapPoNo?: string;
}

export interface ListPurchaseOrderQuery {
  status?: PurchaseOrderStatus | PurchaseOrderStatus[];
  supplierId?: string;
  search?: string;
  includeDeleted?: boolean;
  page?: number;
  limit?: number;
}

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly projector: SnapshotProjectorService,
  ) {}

  private toDate(v?: Date | string | null) {
    return v ? new Date(v) : null;
  }

  /** 占位 SN 生成（v3 业务设计：PO 阶段批量预留，07 RECEIVED 扫码激活换正式 SN）
   *  默认模板 {poNo}-LINE-{NNN}（NNN = PO 内全局 seq，3 位补 0）
   *  支持 PO line 自定义 placeholderPattern；变量：{poNo} / {NNN} / {LINE}
   */
  private generatePlaceholderSn(
    poNo: string,
    globalSeq: number,
    lineNo: number,
    pattern?: string,
  ): string {
    const tmpl = pattern || '{poNo}-LINE-{NNN}';
    return tmpl
      .replace('{poNo}', poNo)
      .replace('{LINE}', String(lineNo).padStart(2, '0'))
      .replace('{NNN}', String(globalSeq).padStart(3, '0'));
  }

  async create(input: CreatePurchaseOrderInput, userId: string, organizationId: string) {
    if (!input.lines || input.lines.length === 0) {
      throw new BadRequestException('PurchaseOrder 必须至少一条 line');
    }
    const supplier = await this.prisma.supplier.findUnique({ where: { id: input.supplierId } });
    if (!supplier || supplier.deletedAt) {
      throw new BadRequestException('Supplier 不存在或已删除');
    }
    // 批量查 SKU 拿 modelId（RobotUnit.modelId 必填，PO line 只有 skuId）
    const skuIds = Array.from(new Set(input.lines.map((l) => l.skuId)));
    const skus = await this.prisma.robotSku.findMany({
      where: { id: { in: skuIds } },
      select: { id: true, modelId: true, enabled: true },
    });
    const skuMap = new Map(skus.map((s) => [s.id, s]));
    for (const l of input.lines) {
      const sku = skuMap.get(l.skuId);
      if (!sku) throw new BadRequestException(`SKU ${l.skuId} 不存在`);
      if (!sku.enabled) throw new BadRequestException(`SKU ${l.skuId} 已停用`);
      if (l.quantity <= 0) throw new BadRequestException(`Line ${l.lineNo} quantity 必须 > 0`);
    }
    const totalAmount = input.lines.reduce((s, l) => s + l.unitPrice * l.quantity, 0);

    return this.prisma.$transaction(async (tx) => {
      // 1. 创建 PO + lines
      const po = await tx.purchaseOrder.create({
        data: {
          poNo: input.poNo,
          supplierId: input.supplierId,
          currencyCode: input.currencyCode,
          totalAmount,
          status: PurchaseOrderStatus.DRAFT,
          orderedAt: new Date(input.orderedAt),
          expectedAt: this.toDate(input.expectedAt),
          notes: input.notes,
          organizationId,
          createdById: userId,
          lines: {
            create: input.lines.map((l) => ({
              lineNo: l.lineNo,
              skuId: l.skuId,
              quantity: l.quantity,
              unitPrice: l.unitPrice,
              totalPrice: l.unitPrice * l.quantity,
              currencyCode: l.currencyCode,
              defaultUsageType: l.defaultUsageType,
              placeholderPattern: l.placeholderPattern,
              expectedAt: this.toDate(l.expectedAt),
            })),
          },
        },
        include: { lines: true },
      });

      // 2. 按 line.quantity 批量创建占位 RobotUnit（v3 业务设计：Sherry PO 阶段就跟进每台进度）
      let globalSeq = 1;
      let totalPlaceholders = 0;
      for (const line of po.lines.sort((a, b) => a.lineNo - b.lineNo)) {
        const inputLine = input.lines.find((l) => l.lineNo === line.lineNo);
        const pattern = inputLine?.placeholderPattern ?? line.placeholderPattern ?? undefined;
        const sku = skuMap.get(line.skuId)!;

        for (let i = 0; i < line.quantity; i++) {
          const ffsn = this.generatePlaceholderSn(po.poNo, globalSeq, line.lineNo, pattern);

          const unit = await tx.robotUnit.create({
            data: {
              organizationId,
              ffsn,
              modelId: sku.modelId,
              skuId: line.skuId,
              usageType: line.defaultUsageType ?? RobotUsageType.SALES,
              purchaseOrderId: po.id,
              purchaseOrderLineId: line.id,
              originalSupplierId: input.supplierId,
              metadata: { isPlaceholder: true } as Prisma.InputJsonValue,
              createdById: userId,
            },
          });

          const event = await tx.robotLifecycleEvent.create({
            data: {
              robotUnitId: unit.id,
              eventType: RobotLifecycleEventType.stage_changed,
              fromStage: null,
              toStage: RobotLifecycleStage.SUPPLY_PO_CREATED,
              actorUserId: userId,
              notes: `PO ${po.poNo} line ${line.lineNo} 占位 SN ${ffsn}`,
              payload: { placeholderSn: ffsn, lineNo: line.lineNo } as Prisma.InputJsonValue,
              occurredAt: new Date(),
              organizationId,
              createdById: userId,
            },
          });

          await this.projector.project(tx, event, undefined);
          globalSeq++;
          totalPlaceholders++;
        }
      }

      this.logger.log(
        `PO ${po.poNo}: ${totalPlaceholders} 占位 RobotUnit 已创建（${po.lines.length} lines）`,
      );

      return po;
    });
  }

  async findAll(query: ListPurchaseOrderQuery) {
    const { page = 1, limit = 20, status, supplierId, search, includeDeleted } = query;
    const where: Prisma.PurchaseOrderWhereInput = {
      ...(includeDeleted ? {} : { deletedAt: null }),
    };
    if (status) where.status = Array.isArray(status) ? { in: status } : status;
    if (supplierId) where.supplierId = supplierId;
    if (search) {
      where.OR = [
        { poNo: { contains: search, mode: 'insensitive' } },
        { sapPoNo: { contains: search, mode: 'insensitive' } },
      ];
    }
    const [items, total] = await Promise.all([
      this.prisma.purchaseOrder.findMany({
        where,
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { orderedAt: 'desc' },
        include: { lines: true, _count: { select: { robotUnits: true } } },
      }),
      this.prisma.purchaseOrder.count({ where }),
    ]);
    return { items, total, page, limit, totalPages: Math.ceil(total / limit) };
  }

  async findOne(id: string) {
    const po = await this.prisma.purchaseOrder.findFirst({
      where: { id, deletedAt: null },
      include: { lines: true, robotUnits: { take: 50, select: { id: true, ffsn: true } } },
    });
    if (!po) throw new NotFoundException(`PurchaseOrder ${id} 不存在`);
    return po;
  }

  @SkipAssertAccess('上方 findFirst({ id }) 已抓到对象，update 用同一 id；DataScope 经 organizationId 索引')
  async update(id: string, input: UpdatePurchaseOrderInput, userId: string) {
    const existing = await this.prisma.purchaseOrder.findFirst({
      where: { id, deletedAt: null },
    });
    if (!existing) throw new NotFoundException(`PurchaseOrder ${id} 不存在`);
    await this.prisma.purchaseOrder.update({
      where: { id },
      data: {
        status: input.status,
        expectedAt: this.toDate(input.expectedAt),
        closedAt: this.toDate(input.closedAt),
        notes: input.notes,
        sapPoNo: input.sapPoNo,
      },
    });
    this.logger.log(`PO ${existing.poNo} updated by ${userId}`);
    return this.findOne(id);
  }

  @SkipAssertAccess('上方 findFirst({ id }) 已抓到对象，soft delete 用同一 id')
  async softDelete(id: string, userId: string) {
    const existing = await this.prisma.purchaseOrder.findFirst({
      where: { id, deletedAt: null },
    });
    if (!existing) throw new NotFoundException(`PurchaseOrder ${id} 不存在`);
    await this.prisma.purchaseOrder.update({
      where: { id },
      data: { deletedAt: new Date() },
    });
    return { message: 'PurchaseOrder soft-deleted', id };
  }
}
