import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { QueryOriginGoodDto } from './dto/query-origin-good.dto';

export interface PaginatedOriginGoods {
  items: Array<{
    id: string;
    sdsGoodId: string;
    goodName: string | null;
    goodImage: string | null;
    goodPrice: string | null;
    sdsCategoryId: string | null;
    createdAt: string;
    updatedAt: string;
    hasDetail: boolean;
    detailSyncedAt: string | null;
    variantCount: number;
  }>;
  total: number;
  page: number;
  pageSize: number;
}

/**
 * A single origin-good node inside the tree, augmented with configuration status
 * (how many `goods` rows reference it and which countries it has been configured for).
 */
export interface OriginGoodsTreeNode {
  id: string;
  goodName: string;
  goodImage: string | null;
  goodPrice: string | null;
  sdsGoodId: string;
  delisted: boolean;
  configuredCount: number;
  configuredCountries: string[];
  configuredTags: { tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: string | null; tagGroupName: string | null; sortOrder: number }[];
  hasDetail: boolean;
  detailSyncedAt: string | null;
  variantCount: number;
  sizeRowCount: number;
  packageRowCount: number;
  familyId: string | null;
  familyName: string | null;
  familyCode: string | null;
  familyStale: boolean | null;
}

/** A category node in the hierarchical tree, with origin goods as leaves. */
export interface OriginGoodsTreeCategoryNode {
  categoryId: string;
  categoryName: string;
  sdsCategoryId: string | null;
  configuredCount: number;
  totalCount: number;
  children: OriginGoodsTreeCategoryNode[];
  originGoods: OriginGoodsTreeNode[];
}

/** Top-level tree response returned by `OriginGoodsService.getTree()`. */
export interface OriginGoodsTreeResponse {
  tree: OriginGoodsTreeCategoryNode[];
  totalOriginGoods: number;
  configuredCount: number;
}

@Injectable()
export class OriginGoodsService {
  constructor(private readonly prisma: PrismaService) {}

  async findAll(query: QueryOriginGoodDto): Promise<PaginatedOriginGoods> {
    const { page, pageSize, keyword } = query;
    const where: Prisma.OriginGoodWhereInput = {
      source: 'SDS',
      ...(keyword
        ? { goodName: { contains: keyword, mode: 'insensitive' as const } }
        : {}),
    };

    const [total, rows] = await this.prisma.$transaction([
      this.prisma.originGood.count({ where }),
      this.prisma.originGood.findMany({
        where,
        orderBy: { id: 'desc' },
        include: { detail: true, _count: { select: { variants: true } } },
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
    ]);

    return {
      items: rows.map((r) => ({
        id: r.id.toString(),
        sdsGoodId: r.sdsGoodId,
        goodName: r.goodName,
        goodImage: r.goodImage,
        goodPrice: r.goodPrice === null || r.goodPrice === undefined ? null : r.goodPrice.toString(),
        sdsCategoryId: r.sdsCategoryId,
        createdAt: r.createdAt.toISOString(),
        updatedAt: r.updatedAt.toISOString(),
        hasDetail: Boolean(r.detail),
        detailSyncedAt: r.detail?.syncedAt.toISOString() ?? null,
        variantCount: r._count.variants,
      })),
      total,
      page,
      pageSize,
    };
  }

  /**
   * Builds a hierarchical tree using the `categories` table parent-child
   * structure, placing each origin-good as a leaf under the category whose
   * `sdsCategoryId` matches the origin-good's `sdsCategoryId`.
   *
   * Origin-goods whose `sdsCategoryId` doesn't map to any category are placed
   * under a synthetic "未分类" root node.
   */
  async getTree(): Promise<OriginGoodsTreeResponse> {
    const [allCategories, allOriginGoods, configCounts, goodsWithCountries, goodsWithTags, mergedCounts, mergedWithCountries] =
      await Promise.all([
        this.prisma.category.findMany({
          where: { sdsCategoryId: { not: null } },
          orderBy: { categoryName: 'asc' },
          select: {
            id: true,
            categoryName: true,
            sdsCategoryId: true,
            parentCategoryId: true,
          },
        }),
        this.prisma.originGood.findMany({
          where: { delisted: false, source: 'SDS' },
          orderBy: { goodName: 'asc' },
          include: {
            detail: true,
            _count: { select: { variants: true } },
            family: { select: { id: true, familyName: true, familyCode: true, stale: true } },
          },
        }),
        this.prisma.good.groupBy({
          by: ['originGoodId'],
          _count: { _all: true },
        }),
        this.prisma.good.findMany({
          select: {
            originGoodId: true,
            country: { select: { countryName: true } },
          },
          distinct: ['originGoodId', 'countryId'],
        }),
        this.prisma.goodTag.findMany({
          select: {
            good: {
              select: {
                originGoodId: true,
                mergedOriginGoods: { select: { originGoodId: true } },
              },
            },
            tag: {
              select: {
                tagName: true,
                tagColor: true,
                tagFontColor: true,
                tagGroupId: true,
                sortOrder: true,
                tagGroup: { select: { groupName: true } },
              },
            },
          },
        }),
        // Secondary-source references (good_origin_goods)
        this.prisma.goodOriginGood.groupBy({
          by: ['originGoodId'],
          _count: { _all: true },
        }),
        this.prisma.goodOriginGood.findMany({
          select: {
            originGoodId: true,
            good: { select: { country: { select: { countryName: true } } } },
          },
        }),
      ]);

    const countMap = new Map<string, number>();
    configCounts.forEach((c) =>
      countMap.set(c.originGoodId.toString(), c._count._all),
    );
    // Secondary (merged) references count towards configured status too.
    mergedCounts.forEach((c) => {
      const key = c.originGoodId.toString();
      countMap.set(key, (countMap.get(key) ?? 0) + c._count._all);
    });

    const countryMap = new Map<string, string[]>();
    const addCountry = (key: string, name?: string | null) => {
      if (!name) return;
      const arr = countryMap.get(key);
      if (!arr?.includes(name)) {
        countryMap.set(key, [...(arr ?? []), name]);
      }
    };
    goodsWithCountries.forEach((g) =>
      addCountry(g.originGoodId.toString(), g.country?.countryName),
    );
    mergedWithCountries.forEach((m) =>
      addCountry(m.originGoodId.toString(), m.good.country?.countryName),
    );

    const tagMap = new Map<string, { tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: string | null; tagGroupName: string | null; sortOrder: number }[]>();
    const addTag = (
      key: string,
      tagInfo: { tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: string | null; tagGroupName: string | null; sortOrder: number },
    ) => {
      const arr = tagMap.get(key);
      if (arr) {
        if (!arr.some((t) => t.tagName === tagInfo.tagName)) arr.push(tagInfo);
      } else {
        tagMap.set(key, [tagInfo]);
      }
    };
    goodsWithTags.forEach((gt) => {
      const tagInfo = {
        tagName: gt.tag.tagName,
        tagColor: gt.tag.tagColor,
        tagFontColor: gt.tag.tagFontColor,
        tagGroupId: gt.tag.tagGroupId?.toString() ?? null,
        tagGroupName: gt.tag.tagGroup?.groupName ?? null,
        sortOrder: gt.tag.sortOrder,
      };
      addTag(gt.good.originGoodId.toString(), tagInfo);
      // A good's tags also mark its secondary origin goods as configured.
      for (const m of gt.good.mergedOriginGoods) {
        addTag(m.originGoodId.toString(), tagInfo);
      }
    });

    const sdsToCategory = new Map<
      string,
      (typeof allCategories)[number]
    >();
    for (const c of allCategories) {
      if (c.sdsCategoryId) sdsToCategory.set(c.sdsCategoryId, c);
    }

    const ogToCategory = new Map<string, string>();
    for (const og of allOriginGoods) {
      if (og.sdsCategoryId && sdsToCategory.has(og.sdsCategoryId)) {
        ogToCategory.set(og.id.toString(), sdsToCategory.get(og.sdsCategoryId)!.id.toString());
      }
    }

    const buildNode = (
      cat: (typeof allCategories)[number],
    ): OriginGoodsTreeCategoryNode => {
      const childrenCats = allCategories.filter(
        (c) => c.parentCategoryId !== null && c.parentCategoryId === cat.id,
      );
      const childNodes = childrenCats
        .map(buildNode)
        .filter((n) => n.totalCount > 0);

      const ogsForThisCat = allOriginGoods.filter(
        (og) => ogToCategory.get(og.id.toString()) === cat.id.toString(),
      );
      const ogNodes: OriginGoodsTreeNode[] = ogsForThisCat.map((og) => ({
        id: og.id.toString(),
        goodName: og.goodName ?? `SDS-${og.sdsGoodId}`,
        goodImage: og.goodImage,
        goodPrice: og.goodPrice?.toString() ?? null,
        sdsGoodId: og.sdsGoodId,
        delisted: og.delisted,
        configuredCount: countMap.get(og.id.toString()) ?? 0,
        configuredCountries: countryMap.get(og.id.toString()) ?? [],
        configuredTags: tagMap.get(og.id.toString()) ?? [],
        hasDetail: Boolean(og.detail),
        detailSyncedAt: og.detail?.syncedAt.toISOString() ?? null,
        variantCount: og._count.variants,
        sizeRowCount: this.jsonRows(og.detail?.sizeChart),
        packageRowCount: this.jsonRows(og.detail?.packageSpecs),
        familyId: og.family?.id.toString() ?? null,
        familyName: og.family?.familyName ?? null,
        familyCode: og.family?.familyCode ?? null,
        familyStale: og.family?.stale ?? null,
      }));

      const childTotal = childNodes.reduce((s, n) => s + n.totalCount, 0);
      const childConfigured = childNodes.reduce(
        (s, n) => s + n.configuredCount,
        0,
      );
      const ogConfigured = ogNodes.filter((o) => o.configuredCount > 0).length;

      return {
        categoryId: cat.id.toString(),
        categoryName: cat.categoryName,
        sdsCategoryId: cat.sdsCategoryId,
        configuredCount: childConfigured + ogConfigured,
        totalCount: childTotal + ogNodes.length,
        children: childNodes,
        originGoods: ogNodes,
      };
    };

    const roots = allCategories.filter((c) => c.parentCategoryId === null);
    const tree = roots.map(buildNode).filter((n) => n.totalCount > 0);

    const unmapped = allOriginGoods.filter(
      (og) => !ogToCategory.has(og.id.toString()),
    );
    if (unmapped.length > 0) {
      tree.push({
        categoryId: 'uncategorized',
        categoryName: '未分类',
        sdsCategoryId: null,
        configuredCount: unmapped.filter(
          (og) => (countMap.get(og.id.toString()) ?? 0) > 0,
        ).length,
        totalCount: unmapped.length,
        children: [],
        originGoods: unmapped.map((og) => ({
          id: og.id.toString(),
          goodName: og.goodName ?? `SDS-${og.sdsGoodId}`,
          goodImage: og.goodImage,
          goodPrice: og.goodPrice?.toString() ?? null,
          sdsGoodId: og.sdsGoodId,
          delisted: og.delisted,
          configuredCount: countMap.get(og.id.toString()) ?? 0,
          configuredCountries: countryMap.get(og.id.toString()) ?? [],
          configuredTags: tagMap.get(og.id.toString()) ?? [],
          hasDetail: Boolean(og.detail),
          detailSyncedAt: og.detail?.syncedAt.toISOString() ?? null,
          variantCount: og._count.variants,
          sizeRowCount: this.jsonRows(og.detail?.sizeChart),
          packageRowCount: this.jsonRows(og.detail?.packageSpecs),
          familyId: og.family?.id.toString() ?? null,
          familyName: og.family?.familyName ?? null,
          familyCode: og.family?.familyCode ?? null,
          familyStale: og.family?.stale ?? null,
        })),
      });
    }

    tree.sort((a, b) => a.categoryName.localeCompare(b.categoryName, 'zh'));

    const totalConfigured = allOriginGoods.filter(
      (og) => (countMap.get(og.id.toString()) ?? 0) > 0,
    ).length;

    return {
      tree,
      totalOriginGoods: allOriginGoods.length,
      configuredCount: totalConfigured,
    };
  }

  private jsonRows(value: unknown): number {
    if (!value || typeof value !== 'object' || !('rows' in value)) return 0;
    const rows = (value as { rows?: unknown }).rows;
    return Array.isArray(rows) ? rows.length : 0;
  }
}
