import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { FamilyRecomputeService } from './family-recompute.service';
import { ProductFamiliesService } from './product-families.service';
import { parseOriginName } from './origin-name.parser';
import {
  DERIVED_TAG_GROUP_SPECS,
  deriveLinkTagNames,
  isAutoTagGroupName,
} from './auto-tag-rules';

/**
 * 整理服务（解析去运行时化的唯一解析入口）：
 * 所有"按上游链接名解析/派生"的逻辑集中于此，由显式人工动作触发——
 * CLI（pnpm --filter @inkreach/api organize）或后台「整理」按钮（POST /product-families/organize）。
 *
 * 运行时（同步/重算/公开读）永不解析名称：
 * - 同步 = 纯镜像；族重算 = 结构化聚合（成员变体 + 链接标签 + 覆盖价）。
 *
 * 人工接管（tagsManual=true）的链接派生永不触碰；幂等可重跑。
 */
@Injectable()
export class OrganizeService {
  private readonly logger = new Logger(OrganizeService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly recompute: FamilyRecomputeService,
    private readonly families: ProductFamiliesService,
  ) {}

  async organize() {
    const labels = await this.backfillLabels();
    const tags = await this.deriveAllTags();
    const grouped = await this.families.autoGroup(true);
    const families = await this.prisma.productFamily.findMany({ select: { id: true } });
    for (const f of families) {
      await this.recompute.recomputeFamily(f.id);
    }
    const result = {
      labelsParsed: labels.parsed,
      labelsUnparsable: labels.unparsable,
      linksUpdated: tags.linksUpdated,
      goodsUpdated: tags.goodsUpdated,
      familiesCreated: grouped.applied,
      familiesRecomputed: families.length,
    };
    this.logger.log(`organize done: ${JSON.stringify(result)}`);
    return result;
  }

  /**
   * 回填结构化解析列（skuCode/logisticsLabel/craftLabel/warehouseLabel）。
   * 只补 NULL 列——存量正确数据不覆盖（同步已不再写入这些列）。
   */
  async backfillLabels(): Promise<{ parsed: number; unparsable: number }> {
    const ogs = await this.prisma.originGood.findMany({
      where: {
        OR: [
          { skuCode: null },
          { logisticsLabel: null },
          { craftLabel: null },
          { warehouseLabel: null },
        ],
      },
      select: { id: true, goodName: true, skuCode: true, logisticsLabel: true, craftLabel: true, warehouseLabel: true },
    });
    let parsed = 0;
    let unparsable = 0;
    for (const og of ogs) {
      const p = parseOriginName(og.goodName);
      const next = {
        skuCode: og.skuCode ?? p.skuCode,
        logisticsLabel: og.logisticsLabel ?? p.logisticsLabel,
        craftLabel: og.craftLabel ?? p.craftLabel,
        warehouseLabel: og.warehouseLabel ?? p.warehouseLabel,
      };
      if (!p.skuCode && !p.craftLabel) unparsable += 1;
      await this.prisma.originGood.update({ where: { id: og.id }, data: next });
      parsed += 1;
    }
    return { parsed, unparsable };
  }

  /** 全量派生：未人工接管的 SDS 链接按名称刷新自动标签，人工接管不动；随后镜像商品并重算受影响族 */
  async deriveAllTags(): Promise<{ linksUpdated: number; goodsUpdated: number; familiesRecomputed: number }> {
    const families = await this.prisma.productFamily.findMany({
      select: { id: true },
      orderBy: { id: 'asc' },
    });
    let linksUpdated = 0;
    let goodsUpdated = 0;
    const touchedFamilies: bigint[] = [];
    for (const f of families) {
      const r = await this.deriveFamilyTags(f.id);
      linksUpdated += r.linksUpdated;
      goodsUpdated += r.goodsUpdated;
      if (r.linksUpdated > 0 || r.goodsUpdated > 0) touchedFamilies.push(f.id);
    }
    // 散链接（无族）也派生并镜像，保证族化之前标签已就绪
    const loose = await this.prisma.originGood.findMany({
      where: { familyId: null, delisted: false, source: 'SDS', tagsManual: false },
      select: { id: true },
    });
    for (const og of loose) {
      const r = await this.deriveTagsForOg(og.id);
      linksUpdated += r.linksUpdated;
    }
    for (const fid of touchedFamilies) {
      await this.recompute.recomputeFamily(fid);
    }
    return { linksUpdated, goodsUpdated, familiesRecomputed: touchedFamilies.length };
  }

  /**
   * 族 → 标签派生（标签与「产品链接」一一对应）：
   * 1) 链接级：未人工接管（tagsManual=false）的 SDS 链接，按链接名称刷新
   *    origin_good_tags 的派生行（manual=false）；人工行（manual=true）永远保留；
   * 2) 商品级镜像：good 标签 = 自身链接的有效标签 ∪ 非自动组的既有标签。
   * 仅更新发生变化的行，幂等。
   */
  async deriveFamilyTags(
    familyId: bigint,
  ): Promise<{ goodsUpdated: number; linksUpdated: number }> {
    const tagMap = await this.ensureDerivedTagMap();
    const family = await this.prisma.productFamily.findUnique({
      where: { id: familyId },
      include: {
        originGoods: {
          where: { delisted: false },
          include: { originGoodTags: true },
        },
      },
    });
    if (!family) return { goodsUpdated: 0, linksUpdated: 0 };

    let linksUpdated = 0;
    for (const og of family.originGoods) {
      if (og.tagsManual || og.source !== 'SDS') continue;
      const derivedIds = this.resolveDerivedIds(og.goodName, tagMap);
      const autoRows = og.originGoodTags.filter((r) => !r.manual);
      const currentIds = autoRows.map((r) => r.tagId).sort((a, b) => Number(a - b));
      const same =
        derivedIds.length === currentIds.length &&
        derivedIds.every((id, i) => id === currentIds[i]);
      if (same) continue;
      await this.prisma.$transaction([
        this.prisma.originGoodTag.deleteMany({
          where: { originGoodId: og.id, manual: false },
        }),
        ...(!derivedIds.length
          ? []
          : [
              this.prisma.originGoodTag.createMany({
                data: derivedIds.map((tagId) => ({
                  originGoodId: og.id,
                  tagId,
                  manual: false,
                })),
              }),
            ]),
      ]);
      linksUpdated += 1;
    }

    // 商品级镜像：派生写入后重新读取链接标签（上面的 include 是派生前快照）
    const familyOgIds = family.originGoods.map((o) => o.id);
    const freshRows = familyOgIds.length
      ? await this.prisma.originGoodTag.findMany({
          where: { originGoodId: { in: familyOgIds } },
        })
      : [];
    const tagsByOg = new Map<string, bigint[]>();
    for (const r of freshRows) {
      const key = r.originGoodId.toString();
      tagsByOg.set(key, [...(tagsByOg.get(key) ?? []), r.tagId]);
    }

    const goods = await this.prisma.good.findMany({
      where: { familyId },
      include: {
        goodTags: { include: { tag: { select: { id: true, tagGroupId: true } } } },
        originGood: { select: { id: true } },
      },
    });
    const strayOgIds = [
      ...new Set(
        goods
          .map((g) => g.originGood?.id.toString())
          .filter((id): id is string => !!id && !tagsByOg.has(id)),
      ),
    ];
    if (strayOgIds.length) {
      const rows = await this.prisma.originGoodTag.findMany({
        where: { originGoodId: { in: strayOgIds.map((v) => BigInt(v)) } },
      });
      for (const r of rows) {
        const key = r.originGoodId.toString();
        tagsByOg.set(key, [...(tagsByOg.get(key) ?? []), r.tagId]);
      }
    }

    const { autoGroupIds } = await this.autoGroupIds();
    let goodsUpdated = 0;
    for (const good of goods) {
      const linkTagIds = good.originGood
        ? (tagsByOg.get(good.originGood.id.toString()) ?? [])
        : [];
      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 })),
              }),
            ]),
      ]);
      goodsUpdated += 1;
    }
    return { goodsUpdated, linksUpdated };
  }

  /**
   * 单链接派生（供「恢复自动」等显式人工动作复用）：
   * 未人工接管时按名称刷新派生标签，并把有效标签镜像到其名下商品。
   */
  async deriveTagsForOg(ogId: bigint): Promise<{ linksUpdated: number }> {
    const og = await this.prisma.originGood.findUnique({
      where: { id: ogId },
      include: { originGoodTags: true },
    });
    if (!og) return { linksUpdated: 0 };
    let linksUpdated = 0;
    if (!og.tagsManual && og.source === 'SDS') {
      const tagMap = await this.ensureDerivedTagMap();
      const derivedIds = this.resolveDerivedIds(og.goodName, tagMap);
      await this.prisma.$transaction([
        this.prisma.originGoodTag.deleteMany({
          where: { originGoodId: og.id, manual: false },
        }),
        ...(!derivedIds.length
          ? []
          : [
              this.prisma.originGoodTag.createMany({
                data: derivedIds.map((tagId) => ({
                  originGoodId: og.id,
                  tagId,
                  manual: false,
                })),
              }),
            ]),
      ]);
      linksUpdated = 1;
    }
    await this.recompute.mirrorLinkTagsToGoods(og.id);
    return { linksUpdated };
  }

  private async autoGroupIds(): Promise<{ autoGroupIds: Set<string> }> {
    const groups = await this.prisma.tagGroup.findMany({
      select: { id: true, groupName: true },
    });
    return {
      autoGroupIds: new Set(
        groups
          .filter((g) => isAutoTagGroupName(g.groupName))
          .map((g) => g.id.toString()),
      ),
    };
  }

  private resolveDerivedIds(
    name: string | null | undefined,
    tagMap: Map<string, bigint>,
  ): bigint[] {
    return [
      ...new Set(
        deriveLinkTagNames(name)
          .map((n) => tagMap.get(n))
          .filter((id): id is bigint => id !== undefined),
      ),
    ].sort((a, b) => Number(a - b));
  }

  /** 确保派生标签组与标签存在，返回「标签名 → 标签 id」映射（并发下取最小 id，天然去重） */
  private async ensureDerivedTagMap(): Promise<Map<string, bigint>> {
    const map = new Map<string, bigint>();
    for (const spec of DERIVED_TAG_GROUP_SPECS) {
      const findGroups = () =>
        this.prisma.tagGroup.findMany({
          where: { groupName: { contains: spec.group } },
          orderBy: { id: 'asc' },
        });
      let group = (await findGroups())[0] ?? null;
      if (!group) {
        try {
          group = await this.prisma.tagGroup.create({ data: { groupName: spec.group } });
        } catch {
          group = (await findGroups())[0] ?? null;
        }
      }
      if (!group) continue;

      const tags = await this.prisma.tag.findMany({
        where: { tagGroupId: group.id, tagName: { in: spec.tags } },
        orderBy: { id: 'asc' },
      });
      for (const tagName of spec.tags) {
        const existing = tags.find((t) => t.tagName === tagName);
        if (existing) {
          map.set(tagName, existing.id);
          continue;
        }
        try {
          const created = await this.prisma.tag.create({
            data: { tagGroupId: group.id, tagName },
          });
          map.set(tagName, created.id);
        } catch {
          const fallback = await this.prisma.tag.findFirst({
            where: { tagGroupId: group.id, tagName },
            orderBy: { id: 'asc' },
          });
          if (fallback) map.set(tagName, fallback.id);
        }
      }
    }
    return map;
  }
}
