import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { FamilyRecomputeService, PriceMatrix } from './family-recompute.service';
import { originGroupKey, parseOriginName } from './origin-name.parser';
import {
  CreateCustomMemberDto,
  CreateProductFamilyDto,
  DeletePriceOverridesDto,
  PatchProductFamilyDto,
  PriceOverrideItemDto,
  QueryProductFamilyDto,
  UpdateFamilyMembersDto,
} from './dto/product-family.dto';

/** 从分类名提取模型编码：`DG001 180G纯棉T恤（JSA002）` → `DG001` */
function codeFromCategoryName(categoryName: string | null | undefined): string | null {
  if (!categoryName) return null;
  const token = categoryName.trim().split(/\s+/)[0] ?? '';
  return /^[A-Za-z0-9]+$/.test(token) ? token : null;
}

const FAMILY_INCLUDE = {  originGoods: {
    orderBy: { id: 'asc' as const },
    select: {
      id: true,
      sdsGoodId: true,
      goodName: true,
      goodImage: true,
      goodPrice: true,
      source: true,
      delisted: true,
      skuCode: true,
      logisticsLabel: true,
      craftLabel: true,
      warehouseLabel: true,
      tagsManual: true,
      originGoodTags: {
        orderBy: { tagId: 'asc' as const },
        select: {
          id: true,
          manual: true,
          tag: { select: { id: true, tagName: true, tagColor: true, tagFontColor: true } },
        },
      },
      _count: { select: { variants: true } },
    },
  },
  priceOverrides: true,
  _count: { select: { originGoods: true, priceOverrides: true } },
} satisfies Prisma.ProductFamilyInclude;

@Injectable()
export class ProductFamiliesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly recompute: FamilyRecomputeService,
  ) {}

  async list(query: QueryProductFamilyDto) {
    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 20;
    const where: Prisma.ProductFamilyWhereInput = query.keyword
      ? {
          OR: [
            { familyName: { contains: query.keyword, mode: 'insensitive' } },
            { familyCode: { contains: query.keyword, mode: 'insensitive' } },
          ],
        }
      : {};
    const [items, total] = await this.prisma.$transaction([
      this.prisma.productFamily.findMany({
        where,
        include: { _count: { select: { originGoods: true, priceOverrides: true } } },
        orderBy: { updatedAt: 'desc' },
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
      this.prisma.productFamily.count({ where }),
    ]);
    return { items, total, page, pageSize };
  }

  async detail(id: bigint) {
    const family = await this.prisma.productFamily.findUnique({
      where: { id },
      include: FAMILY_INCLUDE,
    });
    if (!family) throw new NotFoundException(`product family ${id} not found`);
    return family;
  }

  async create(dto: CreateProductFamilyDto) {
    const family = await this.prisma.productFamily.create({
      data: {
        familyName: dto.familyName,
        familyCode: dto.familyCode ? await this.ensureUniqueCode(dto.familyCode) : null,
        familyImage: dto.familyImage ?? null,
        countryId: dto.countryId ? BigInt(dto.countryId) : null,
        categoryId: dto.categoryId ? BigInt(dto.categoryId) : null,
        primaryOriginGoodId: dto.primaryOriginGoodId ? BigInt(dto.primaryOriginGoodId) : null,
      },
    });
    if (dto.originGoodIds?.length) {
      await this.attachMembers(
        family.id,
        dto.originGoodIds.map((v) => BigInt(v)),
      );
    }
    await this.recompute.recomputeFamily(family.id);
    return this.detail(family.id);
  }

  async patch(id: bigint, dto: PatchProductFamilyDto) {
    const existing = await this.prisma.productFamily.findUnique({ where: { id } });
    if (!existing) throw new NotFoundException(`product family ${id} not found`);

    const data: Prisma.ProductFamilyUpdateInput = {};
    if (dto.familyName !== undefined) data.familyName = dto.familyName;
    if (dto.familyImage !== undefined) data.familyImage = dto.familyImage;
    if (dto.autoManaged !== undefined) data.autoManaged = dto.autoManaged;
    if (dto.countryId !== undefined) {
      data.country = dto.countryId
        ? { connect: { id: BigInt(dto.countryId) } }
        : { disconnect: true };
    }
    if (dto.categoryId !== undefined) {
      data.category = dto.categoryId
        ? { connect: { id: BigInt(dto.categoryId) } }
        : { disconnect: true };
    }
    if (dto.familyCode !== undefined) {
      data.familyCode =
        dto.familyCode === ''
          ? null
          : dto.familyCode !== existing.familyCode
            ? await this.ensureUniqueCode(dto.familyCode, id)
            : existing.familyCode;
    }
    if (dto.primaryOriginGoodId !== undefined) {
      data.primaryOriginGoodId = dto.primaryOriginGoodId ? BigInt(dto.primaryOriginGoodId) : null;
    }

    await this.prisma.productFamily.update({ where: { id }, data });
    await this.recompute.recomputeFamily(id);
    return this.detail(id);
  }

  /** 自动成族：SDS 叶子分类即产品模型（如 "DG001 180G纯棉T恤（JSA002）"），
   *  同分类链接归一族（跨工艺/物流/仓库/编码）；无分类回退名称 3 段键；apply=false 仅预览 */
  async autoGroup(apply: boolean) {
    const candidates = await this.prisma.originGood.findMany({
      where: { familyId: null, delisted: false },
      select: { id: true, goodName: true, goodImage: true, source: true, sdsCategoryId: true },
      orderBy: { id: 'asc' },
    });
    const categories = await this.prisma.category.findMany({
      where: { sdsCategoryId: { not: null } },
      select: { sdsCategoryId: true, categoryName: true },
    });
    const catName = new Map(categories.map((c) => [c.sdsCategoryId as string, c.categoryName]));

    const groups = new Map<string, typeof candidates>();
    for (const og of candidates) {
      const nameKey = originGroupKey(og.goodName);
      const key = og.sdsCategoryId ? `cat:${og.sdsCategoryId}` : nameKey ? `name:${nameKey}` : '';
      if (!key) continue;
      const arr = groups.get(key);
      if (arr) arr.push(og);
      else groups.set(key, [og]);
    }

    const preview = [...groups.values()].map((members) => {
      const parsed = parseOriginName(members[0].goodName);
      const categoryName = members[0].sdsCategoryId
        ? (catName.get(members[0].sdsCategoryId) ?? null)
        : null;
      return {
        groupKey: members[0].sdsCategoryId ? `cat:${members[0].sdsCategoryId}` : `name:${originGroupKey(members[0].goodName)}`,
        familyName:
          categoryName ?? parsed.productName ?? originGroupKey(members[0].goodName),
        familyCode: codeFromCategoryName(categoryName) ?? parsed.skuCode ?? null,
        memberCount: members.length,
        sampleNames: members.slice(0, 3).map((m) => m.goodName ?? ''),
      };
    });

    if (!apply) return { applied: 0, groups: preview };

    let applied = 0;
    for (const members of groups.values()) {
      const parsed = parseOriginName(members[0].goodName);
      const categoryName = members[0].sdsCategoryId
        ? (catName.get(members[0].sdsCategoryId) ?? null)
        : null;
      const fallbackCode =
        members[0].source === 'CUSTOM' ? `CUSTOM-${members[0].id}` : null;
      const family = await this.prisma.productFamily.create({
        data: {
          familyName: categoryName ?? parsed.productName ?? originGroupKey(members[0].goodName),
          familyCode:
            codeFromCategoryName(categoryName) ?? parsed.skuCode ?? fallbackCode
              ? await this.ensureUniqueCode(
                  (codeFromCategoryName(categoryName) ?? parsed.skuCode ?? fallbackCode)!,
                )
              : null,
          familyImage: members[0].goodImage ?? null,
          primaryOriginGoodId: members[0].id,
        },
      });
      // 宽容挂载：并行环境下成员可能在候选查询后消失（如测试清理），跳过即可
      await this.attachMembers(
        family.id,
        members.map((m) => m.id),
        { strict: false },
      );
      await this.recompute.recomputeFamily(family.id);
      applied += 1;
    }
    return { applied, groups: preview };
  }

  async updateMembers(id: bigint, dto: UpdateFamilyMembersDto) {
    const family = await this.prisma.productFamily.findUnique({ where: { id } });
    if (!family) throw new NotFoundException(`product family ${id} not found`);

    if (dto.removeOriginGoodIds?.length) {
      const removeIds = dto.removeOriginGoodIds.map((v) => BigInt(v));
      const remaining = await this.prisma.originGood.count({
        where: { familyId: id, id: { notIn: removeIds } },
      });
      await this.prisma.originGood.updateMany({
        where: { id: { in: removeIds }, familyId: id },
        data: { familyId: null },
      });
      await this.prisma.good.updateMany({
        where: { originGoodId: { in: removeIds }, familyId: id },
        data: { familyId: null },
      });
      // 移除的是主链接（或主链接已不在族内）→ 落到剩余第一个成员
      if (remaining > 0) {
        const stillPrimary = await this.prisma.originGood.count({
          where: { familyId: id, id: family.primaryOriginGoodId ?? -1n },
        });
        if (!stillPrimary) {
          const next = await this.prisma.originGood.findFirst({
            where: { familyId: id },
            orderBy: { id: 'asc' },
            select: { id: true },
          });
          if (next) {
            await this.prisma.productFamily.update({
              where: { id },
              data: { primaryOriginGoodId: next.id },
            });
          }
        }
      } else {
        await this.prisma.productFamily.update({
          where: { id },
          data: { primaryOriginGoodId: null },
        });
      }
    }

    if (dto.addOriginGoodIds?.length) {
      const addIds = dto.addOriginGoodIds.map((v) => BigInt(v));
      await this.attachMembers(id, addIds);
      await this.prisma.good.updateMany({
        where: { originGoodId: { in: addIds } },
        data: { familyId: id },
      });
    }

    await this.recompute.recomputeFamily(id);
    return this.detail(id);
  }

  /** 在族内创建自定义成员（人工商品），成功后重算 */
  async createCustomMember(familyId: bigint, dto: CreateCustomMemberDto) {
    const family = await this.prisma.productFamily.findUnique({ where: { id: familyId } });
    if (!family) throw new NotFoundException(`product family ${familyId} not found`);

    const { randomUUID } = await import('node:crypto');
    const originGood = await this.prisma.originGood.create({
      data: {
        sdsGoodId: `custom-${randomUUID()}`,
        goodName: dto.goodName,
        goodImage: dto.goodImage ?? null,
        source: 'CUSTOM',
        familyId,
        skuCode: dto.skuCode ?? null,
        logisticsLabel: dto.logisticsLabel,
        craftLabel: dto.craftLabel,
        warehouseLabel: dto.warehouseLabel ?? null,
      },
    });
    await this.prisma.originGoodVariant.createMany({
      data: dto.variants.map((v) => ({
        originGoodId: originGood.id,
        sdsVariantId: `custom-${randomUUID()}`,
        sku: v.sku,
        sizeId: v.sizeId ?? null,
        sizeName: v.sizeName ?? null,
        colorId: v.colorId ?? null,
        colorName: v.colorName ?? null,
        colorHex: v.colorHex ?? null,
        imageUrl: v.imageUrl ?? null,
        price: new Prisma.Decimal(v.price),
      })),
    });
    if (dto.detail?.sizeChart || dto.detail?.packageSpecs) {
      await this.prisma.originGoodDetail.create({
        data: {
          originGoodId: originGood.id,
          sizeChart: (dto.detail?.sizeChart ?? undefined) as Prisma.InputJsonValue,
          packageSpecs: (dto.detail?.packageSpecs ?? undefined) as Prisma.InputJsonValue,
        },
      });
    }
    if (!family.primaryOriginGoodId) {
      await this.prisma.productFamily.update({
        where: { id: familyId },
        data: { primaryOriginGoodId: originGood.id },
      });
    }
    await this.recompute.recomputeFamily(familyId);
    return originGood;
  }

  async listPriceOverrides(id: bigint) {
    const family = await this.prisma.productFamily.findUnique({ where: { id } });
    if (!family) throw new NotFoundException(`product family ${id} not found`);
    const overrides = await this.prisma.familyPriceOverride.findMany({
      where: { familyId: id },
      orderBy: { updatedAt: 'desc' },
    });
    const matrix = (family.priceMatrix as PriceMatrix | null) ?? null;
    const rows = matrix?.rows ?? [];
    return {
      items: overrides.map((o) => {
        // 推导价 = 该格子全部来源中的最低价（覆盖生效前的推导结果，保留在 sources 里）
        const row = rows.find(
          (r) =>
            r.sizeId === o.sizeId &&
            r.colorId === o.colorId &&
            r.printCount === o.printCount &&
            r.craft === o.craft &&
            r.logistics === o.logistics,
        );
        const derived =
          row?.sources.length && !row.manual
            ? row.price
            : row?.sources.length
              ? String(Math.min(...row.sources.map((s) => Number(s.price))))
              : null;
        return {
          ...o,
          derivedPrice: derived,
          diff: derived !== null ? (Number(o.price) - Number(derived)).toFixed(2) : null,
        };
      }),
    };
  }

  async putPriceOverrides(id: bigint, items: PriceOverrideItemDto[]) {
    const family = await this.prisma.productFamily.findUnique({ where: { id } });
    if (!family) throw new NotFoundException(`product family ${id} not found`);

    // 矩阵未物化（如刚建族）时先重算，保证维度校验有依据
    let matrix = family.priceMatrix as PriceMatrix | null;
    if (!matrix) {
      await this.recompute.recomputeFamily(id);
      const refreshed = await this.prisma.productFamily.findUnique({ where: { id } });
      matrix = (refreshed?.priceMatrix as PriceMatrix | null) ?? null;
    }
    const allowed = {
      sizes: new Set((matrix?.sizes ?? []).map((s) => s.key)),
      colors: new Set((matrix?.colors ?? []).map((c) => c.key)),
      printCounts: new Set(matrix?.printCounts ?? []),
      crafts: new Set(matrix?.crafts ?? []),
      logistics: new Set(matrix?.logistics ?? []),
    };
    const invalid = items.filter(
      (i) =>
        !allowed.sizes.has(i.sizeId) ||
        !allowed.colors.has(i.colorId) ||
        !allowed.printCounts.has(i.printCount) ||
        !allowed.crafts.has(i.craft) ||
        !allowed.logistics.has(i.logistics),
    );
    if (invalid.length) {
      throw new BadRequestException({
        message: 'price override dimensions must exist in the family matrix',
        invalidCells: invalid.map((i) => ({
          sizeId: i.sizeId,
          colorId: i.colorId,
          printCount: i.printCount,
          craft: i.craft,
          logistics: i.logistics,
        })),
      });
    }

    for (const item of items) {
      await this.prisma.familyPriceOverride.upsert({
        where: {
          familyId_sizeId_colorId_printCount_craft_logistics: {
            familyId: id,
            sizeId: item.sizeId,
            colorId: item.colorId,
            printCount: item.printCount,
            craft: item.craft,
            logistics: item.logistics,
          },
        },
        create: {
          familyId: id,
          sizeId: item.sizeId,
          colorId: item.colorId,
          printCount: item.printCount,
          craft: item.craft,
          logistics: item.logistics,
          price: new Prisma.Decimal(item.price),
          note: item.note ?? null,
        },
        update: {
          price: new Prisma.Decimal(item.price),
          note: item.note ?? null,
        },
      });
    }
    await this.recompute.recomputeFamily(id);
    return this.listPriceOverrides(id);
  }

  async deletePriceOverrides(id: bigint, dto: DeletePriceOverridesDto) {
    for (const cell of dto.cells) {
      await this.prisma.familyPriceOverride.deleteMany({
        where: {
          familyId: id,
          sizeId: cell.sizeId,
          colorId: cell.colorId,
          printCount: cell.printCount,
          craft: cell.craft,
          logistics: cell.logistics,
        },
      });
    }
    await this.recompute.recomputeFamily(id);
    return this.listPriceOverrides(id);
  }

  async recomputeNow(id: bigint) {
    const family = await this.prisma.productFamily.findUnique({ where: { id } });
    if (!family) throw new NotFoundException(`product family ${id} not found`);
    await this.recompute.recomputeFamily(id);
    return this.detail(id);
  }

  private async attachMembers(
    familyId: bigint,
    originGoodIds: bigint[],
    opts: { strict?: boolean } = {},
  ) {
    if (!originGoodIds.length) return;
    const existings = await this.prisma.originGood.findMany({
      where: { id: { in: originGoodIds } },
      select: { id: true },
    });
    const existingIds = new Set(existings.map((e) => e.id.toString()));
    const missing = originGoodIds.filter((v) => !existingIds.has(v.toString()));
    if (missing.length && opts.strict !== false) {
      throw new BadRequestException({
        message: 'origin goods not found',
        ids: missing.map(String),
      });
    }
    const attachable = originGoodIds.filter((v) => existingIds.has(v.toString()));
    if (!attachable.length) return;
    await this.prisma.originGood.updateMany({
      where: { id: { in: attachable } },
      data: { familyId },
    });
  }

  /** familyCode 全局唯一：冲突时追加 -2/-3… 后缀（不同国家同 SKU 常见） */
  private async ensureUniqueCode(code: string, selfId?: bigint): Promise<string> {
    let candidate = code;
    let seq = 2;
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const clash = await this.prisma.productFamily.findFirst({
        where: { familyCode: candidate, ...(selfId ? { id: { not: selfId } } : {}) },
        select: { id: true },
      });
      if (!clash) return candidate;
      candidate = `${code}-${seq++}`;
    }
  }
}
