import { Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { isAutoTagGroupName } from './auto-tag-rules';

/**
 * 产品族重算：并集尺码表/包装规则 + 五维价格矩阵物化。
 * 设计：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;
  /** 印花数量维：单面印花 / 双面印花 */
  printCount: string;
  /** 工艺维：烫画 / 直喷 / 不打印 */
  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 }>;
  printCounts: string[];
  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;
    originGoodTags: { select: { tag: { select: { tagName: true } } } };
  };
}>;
type OverrideRow = Prisma.FamilyPriceOverrideGetPayload<{}>;

/** 矩阵三个归因维度的合法取值（封闭集合，与派生标签组一致） */
const DIM_VALUES = {
  printCount: ['单面印花', '双面印花'],
  craft: ['烫画', '直喷', '不打印'],
  logistics: ['包邮', '不包邮'],
} as const satisfies Record<string, readonly string[]>;

type DimKey = keyof typeof DIM_VALUES;
export interface MatrixCombo {
  printCount: string;
  craft: string;
  logistics: string;
}

/**
 * 成员在矩阵中的归因维度组合（纯函数），取值只有两个来源——
 * 1. 链接有效标签（人工接管后仍准确，仅认封闭词表内的标签）；
 * 2. CUSTOM 成员的管理员显式标签（craftLabel/logisticsLabel，自由文本）。
 * 【不解析名称】：SDS 成员无标签 → 返回空（不进矩阵），由整理（OrganizeService）显式补标签。
 * 多值时取笛卡尔积 —— 一个链接理论上只属一个组合，此处只是容错。
 */
export function memberMatrixCombos(input: {
  tagNames: string[];
  customLabels?: { craft?: string | null; logistics?: string | null };
}): MatrixCombo[] {
  const values = (dim: DimKey): string[] =>
    DIM_VALUES[dim].filter((v) => input.tagNames.includes(v));

  let printCounts = values('printCount');
  let crafts = values('craft');
  let logistics = values('logistics');
  // CUSTOM 成员：管理员显式标签是唯一来源（craftLabel/logisticsLabel，自由文本）
  const craftLabel = input.customLabels?.craft ?? '';
  const logisticsLabel = input.customLabels?.logistics ?? '';
  if (!crafts.length && craftLabel) crafts = [craftLabel];
  if (!logistics.length && logisticsLabel) logistics = [logisticsLabel];
  // 结构化占位规则（非名称解析）：工艺=不打印 时印花面不存在，印花数量固定单面占位，
  // 保证光板链接的价格进矩阵；工艺=烫画/直喷 而缺印花数量标签 → 缺维度不进矩阵（等整理补标签）
  const noPrintCraft =
    crafts.includes('不打印') || craftLabel.includes('不打印') || craftLabel.includes('光板');
  if (!printCounts.length && noPrintCraft) {
    printCounts = [DIM_VALUES.printCount[0]];
  } else if (!printCounts.length && craftLabel.includes('双面')) {
    printCounts = ['双面印花'];
  }
  if (!printCounts.length || !crafts.length || !logistics.length) return [];

  const combos: MatrixCombo[] = [];
  for (const printCount of printCounts) {
    for (const craft of crafts) {
      for (const logistic of logistics) {
        combos.push({ printCount, craft, logistics: logistic });
      }
    }
  }
  return combos;
}

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, printCount, craft, logistics) → 价格。
 * 尺寸键 = variant.sizeId ?? variant.sizeName；颜色键 = variant.colorId ?? variant.colorName；
 * 归因维度取成员链接标签（见 memberMatrixCombos）。
 * 同格子多来源取最低价，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>();

  const memberCombos = members.map((member) => ({
    member,
    combos: memberMatrixCombos({
      tagNames: member.originGoodTags.map((r) => r.tag.tagName),
      // CUSTOM 成员无同步标签，管理员显式填写的标签字段是其唯一归因来源
      customLabels:
        member.source === 'CUSTOM'
          ? { craft: member.craftLabel, logistics: member.logisticsLabel }
          : undefined,
    }),
  }));

  const noteVariant = (variant: Member['variants'][number]) => {
    const sizeKey = String(variant.sizeId ?? variant.sizeName ?? '');
    const colorKey = String(variant.colorId ?? variant.colorName ?? '');
    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,
      });
    }
    return { sizeKey, colorKey };
  };

  for (const { member, combos } of memberCombos) {
    for (const combo of combos) {
      for (const variant of member.variants) {
        if (variant.price === null) continue;
        const { sizeKey, colorKey } = noteVariant(variant);
        if (!sizeKey && !colorKey) continue;
        const key = `${sizeKey}|${colorKey}|${combo.printCount}|${combo.craft}|${combo.logistics}`;
        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,
            printCount: combo.printCount,
            craft: combo.craft,
            logistics: combo.logistics,
            price: priceStr,
            manual: false,
            sources: [source],
          });
        } else {
          existing.sources.push(source);
          if (Number(variant.price) < Number(existing.price)) existing.price = priceStr;
        }
      }
    }
  }

  const printCounts = [
    ...new Set(memberCombos.flatMap(({ combos }) => combos.map((c) => c.printCount))),
  ];
  const crafts = [...new Set(memberCombos.flatMap(({ combos }) => combos.map((c) => c.craft)))];
  const logisticsOptions = [
    ...new Set(memberCombos.flatMap(({ combos }) => combos.map((c) => c.logistics))),
  ];

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

  // 人工覆盖：命中改价，未命中新增行（人工补组合），选项并入覆盖用到的取值
  for (const override of overrides) {
    if (!printCounts.includes(override.printCount)) printCounts.push(override.printCount);
    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.printCount}|${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,
        printCount: override.printCount,
        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 })),
    printCounts,
    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' } },
            originGoodTags: { select: { tag: { select: { tagName: true } } } },
          },
        },
        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 },
      });
    }

  }
  /** 把链接的有效标签镜像到其名下商品（good 标签 = 链接标签 ∪ 非自动组既有标签） */
  async mirrorLinkTagsToGoods(ogId: bigint): Promise<void> {
    const rows = await this.prisma.originGoodTag.findMany({
      where: { originGoodId: ogId },
      select: { tagId: true },
    });
    const linkTagIds = rows.map((r) => r.tagId);
    const goods = await this.prisma.good.findMany({
      where: { originGoodId: ogId },
      include: {
        goodTags: { include: { tag: { select: { id: true, tagGroupId: true } } } },
      },
    });
    if (!goods.length) return;
    const groups = await this.prisma.tagGroup.findMany({
      select: { id: true, groupName: true },
    });
    const autoGroupIds = new Set(
      groups.filter((g) => isAutoTagGroupName(g.groupName)).map((g) => g.id.toString()),
    );
    for (const good of goods) {
      const keep = good.goodTags
        .filter((gt) => !autoGroupIds.has(gt.tag.tagGroupId?.toString() ?? ''))
        .map((gt) => gt.tagId);
      const target = [...new Set([...keep, ...linkTagIds])].sort((a, b) =>
        Number(a - b),
      );
      const current = good.goodTags.map((gt) => gt.tagId).sort((a, b) => Number(a - b));
      const same =
        target.length === current.length && target.every((id, i) => id === current[i]);
      if (same) continue;
      await this.prisma.$transaction([
        this.prisma.goodTag.deleteMany({ where: { goodId: good.id } }),
        ...(!target.length
          ? []
          : [
              this.prisma.goodTag.createMany({
                data: target.map((tagId) => ({ goodId: good.id, tagId })),
              }),
            ]),
      ]);
    }
  }


}
