import { Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';

/**
 * 产品族重算：并集尺码表/包装规则 + 五维价格矩阵物化。
 * 设计：docs/superpowers/specs/2026-08-28-product-family-merge-design.md §7-§9
 *
 * 关键不变量：
 * - 推导输入全部为确定性数据（成员镜像、覆盖表），重算幂等；
 * - autoManaged=false 的族只置 stale，绝不覆盖人工物化字段；
 * - 覆盖表与自定义成员数据只读，永不写回。
 */

export interface PriceMatrixSource {
  sdsGoodId: string;
  sdsVariantId: string;
  price: string;
}

export interface PriceMatrixRow {
  sizeId: string;
  sizeName: string | null;
  colorId: string;
  colorName: string | null;
  craft: string;
  logistics: string;
  price: string;
  manual: boolean;
  sources: PriceMatrixSource[];
}

export interface PriceMatrix {
  sizes: Array<{ key: string; name: string | null }>;
  colors: Array<{ key: string; name: string | null; hex: string | null; imageUrl: string | null }>;
  crafts: string[];
  logistics: string[];
  rows: PriceMatrixRow[];
}

export interface ChartSizeRow {
  sizeId?: string | null;
  sizeName?: string | null;
  [key: string]: unknown;
}

export interface ChartLike {
  columns?: unknown;
  rows?: ChartSizeRow[];
}

type Member = Prisma.OriginGoodGetPayload<{
  include: { detail: true; variants: true };
}>;
type OverrideRow = Prisma.FamilyPriceOverrideGetPayload<{}>;

function chartRowKey(row: ChartSizeRow): string {
  return String(row.sizeName ?? row.sizeId ?? '');
}

/**
 * 按优先级顺序（已排序的成员 → 各自的 chart）合并尺码行并集。
 * 同键冲突时先到先得 —— 调用方负责把"覆盖最全的成员 / 主链接"排在前面。
 */
export function unionChartRows(charts: Array<ChartLike | null | undefined>): ChartSizeRow[] {
  const out: ChartSizeRow[] = [];
  const seen = new Set<string>();
  for (const chart of charts) {
    for (const row of chart?.rows ?? []) {
      const key = chartRowKey(row);
      if (!key || seen.has(key)) continue;
      seen.add(key);
      out.push(row);
    }
  }
  return out;
}

function firstColumns(charts: Array<ChartLike | null | undefined>): unknown {
  for (const chart of charts) {
    if (chart?.columns !== undefined && chart?.columns !== null) return chart.columns;
  }
  return undefined;
}

/**
 * 推导五维价格矩阵：(sizeKey, colorKey, craft, logistics) → 价格。
 * 尺寸键 = variant.sizeId ?? variant.sizeName；颜色键 = variant.colorId ?? variant.colorName。
 * 同格子多来源取最低价，sources 全保留；随后合并人工覆盖（manual=true）。
 */
export function derivePriceMatrix(members: Member[], overrides: OverrideRow[]): PriceMatrix {
  const sizes = new Map<string, string | null>();
  const colors = new Map<string, { name: string | null; hex: string | null; imageUrl: string | null }>();
  const cellMap = new Map<string, PriceMatrixRow>();

  for (const member of members) {
    if (!member.craftLabel || !member.logisticsLabel) continue;
    for (const variant of member.variants) {
      if (variant.price === null) continue;
      const sizeKey = String(variant.sizeId ?? variant.sizeName ?? '');
      const colorKey = String(variant.colorId ?? variant.colorName ?? '');
      if (!sizeKey && !colorKey) continue;
      if (sizeKey && !sizes.has(sizeKey)) sizes.set(sizeKey, variant.sizeName);
      if (colorKey) {
        const prev = colors.get(colorKey);
        colors.set(colorKey, {
          name: prev?.name ?? variant.colorName,
          hex: prev?.hex ?? variant.colorHex,
          imageUrl: prev?.imageUrl ?? variant.imageUrl,
        });
      }
      const key = `${sizeKey}|${colorKey}|${member.craftLabel}|${member.logisticsLabel}`;
      const priceStr = variant.price.toString();
      const source: PriceMatrixSource = {
        sdsGoodId: member.sdsGoodId,
        sdsVariantId: variant.sdsVariantId,
        price: priceStr,
      };
      const existing = cellMap.get(key);
      if (!existing) {
        cellMap.set(key, {
          sizeId: sizeKey,
          sizeName: variant.sizeName,
          colorId: colorKey,
          colorName: variant.colorName,
          craft: member.craftLabel,
          logistics: member.logisticsLabel,
          price: priceStr,
          manual: false,
          sources: [source],
        });
      } else {
        existing.sources.push(source);
        if (Number(variant.price) < Number(existing.price)) existing.price = priceStr;
      }
    }
  }

  const crafts = [...new Set(members.map((m) => m.craftLabel).filter((c): c is string => !!c))];
  const logisticsOptions = [
    ...new Set(members.map((m) => m.logisticsLabel).filter((l): l is string => !!l)),
  ];

  const rows = [...cellMap.values()];

  // 人工覆盖：命中改价，未命中新增行（人工补组合），选项并入覆盖用到的取值
  for (const override of overrides) {
    if (!crafts.includes(override.craft)) crafts.push(override.craft);
    if (!logisticsOptions.includes(override.logistics)) logisticsOptions.push(override.logistics);
    if (override.sizeId && !sizes.has(override.sizeId)) sizes.set(override.sizeId, null);
    if (override.colorId && !colors.has(override.colorId)) {
      colors.set(override.colorId, { name: null, hex: null, imageUrl: null });
    }
    const key = `${override.sizeId}|${override.colorId}|${override.craft}|${override.logistics}`;
    const row = cellMap.get(key);
    if (row) {
      row.price = override.price.toString();
      row.manual = true;
    } else {
      const added: PriceMatrixRow = {
        sizeId: override.sizeId,
        sizeName: sizes.get(override.sizeId) ?? null,
        colorId: override.colorId,
        colorName: colors.get(override.colorId)?.name ?? null,
        craft: override.craft,
        logistics: override.logistics,
        price: override.price.toString(),
        manual: true,
        sources: [],
      };
      cellMap.set(key, added);
      rows.push(added);
    }
  }

  return {
    sizes: [...sizes.entries()].map(([key, name]) => ({ key, name })),
    colors: [...colors.entries()].map(([key, v]) => ({ key, ...v })),
    crafts,
    logistics: logisticsOptions,
    rows,
  };
}

/** canonical detail 的文本字段（从主链接 detail 摘取，不含 JSON 物化与时间戳） */
function pickDetailText(detail: Prisma.OriginGoodDetailGetPayload<{}>): Prisma.InputJsonValue {
  return {
    productCode: detail.productCode,
    englishName: detail.englishName,
    blankDesignUrl: detail.blankDesignUrl,
    detailsPageVideoUrl: detail.detailsPageVideoUrl,
    textureName: detail.textureName,
    productionCycleHours: detail.productionCycleHours,
    minWeightG: detail.minWeightG ? detail.minWeightG.toString() : null,
    reminder: detail.reminder,
    productionProcess: detail.productionProcess,
    materialDescription: detail.materialDescription,
    productPerformance: detail.productPerformance,
    applicableScenarios: detail.applicableScenarios,
    washingInstructions: detail.washingInstructions,
    specialDescription: detail.specialDescription,
    designExplanation: detail.designExplanation,
    designArea: detail.designArea,
    pictureRequest: detail.pictureRequest,
  };
}

const json = (value: unknown): Prisma.InputJsonValue | typeof Prisma.DbNull =>
  (value === undefined || value === null ? Prisma.DbNull : value) as Prisma.InputJsonValue;

@Injectable()
export class FamilyRecomputeService {
  private readonly logger = new Logger(FamilyRecomputeService.name);
  private readonly pending = new Map<string, Promise<void>>();

  constructor(private readonly prisma: PrismaService) {}

  /** 进程内去重的异步重算入口（同步钩子用） */
  enqueue(familyId: bigint): void {
    const key = familyId.toString();
    if (this.pending.has(key)) return;
    const run = this.recomputeFamily(familyId)
      .catch((error) => {
        this.logger.error(`family ${key} recompute failed: ${String(error)}`);
      })
      .finally(() => {
        this.pending.delete(key);
      });
    this.pending.set(key, run);
  }

  async recomputeFamily(familyId: bigint): Promise<void> {
    const family = await this.prisma.productFamily.findUnique({
      where: { id: familyId },
      include: {
        originGoods: {
          where: { delisted: false },
          include: {
            detail: true,
            variants: { where: { enabled: true }, orderBy: { sortOrder: 'asc' } },
          },
        },
        priceOverrides: true,
      },
    });
    if (!family) return;

    const members = family.originGoods;
    const primary = members.find((m) => m.id === family.primaryOriginGoodId) ?? members[0] ?? null;

    // 冲突裁决优先级：尺码覆盖最全 → 主链接 → id 升序（与设计 §7 一致）
    const priority = [...members].sort((a, b) => {
      const aRows = a.detail?.sizeChart ? ((a.detail.sizeChart as ChartLike).rows?.length ?? 0) : 0;
      const bRows = b.detail?.sizeChart ? ((b.detail.sizeChart as ChartLike).rows?.length ?? 0) : 0;
      if (bRows !== aRows) return bRows - aRows;
      const aPrimary = a.id === primary?.id ? 1 : 0;
      const bPrimary = b.id === primary?.id ? 1 : 0;
      if (aPrimary !== bPrimary) return bPrimary - aPrimary;
      return Number(a.id - b.id);
    });

    const sizeCharts = priority.map((m) => m.detail?.sizeChart as ChartLike | undefined);
    const packageCharts = priority.map((m) => m.detail?.packageSpecs as ChartLike | undefined);
    const sizeChart = unionChartRows(sizeCharts).length
      ? { columns: firstColumns(sizeCharts), rows: unionChartRows(sizeCharts) }
      : null;
    const packageSpecs = unionChartRows(packageCharts).length
      ? { columns: firstColumns(packageCharts), rows: unionChartRows(packageCharts) }
      : null;
    const matrix = derivePriceMatrix(members, family.priceOverrides);

    if (family.autoManaged) {
      await this.prisma.productFamily.update({
        where: { id: family.id },
        data: {
          sizeChart: json(sizeChart),
          packageSpecs: json(packageSpecs),
          priceMatrix: json(matrix),
          stale: false,
          // canonical detail 仅在为空时从主链接初始化一次，人工编辑后永不被覆盖
          ...(family.detail === null && primary?.detail
            ? { detail: pickDetailText(primary.detail) }
            : {}),
        },
      });
    } else {
      await this.prisma.productFamily.update({
        where: { id: family.id },
        data: { stale: true },
      });
    }
  }
}
