import mysql, { type Connection } from "mysql2/promise";
import type { AppConfig } from "../../shared/types";

export interface ProductPriceRecord {
  source: string;
  productName: string;
  price: string;
  note: string;
  evidence: string;
}

export interface ProductSummaryRecord {
  source: string;
  productName: string;
  brandName: string;
  price: string;
  status: string;
  description: string;
  note: string;
  evidence: string;
}

export interface OrderCountQuery {
  mobile?: string;
  dateFrom?: string;
  dateTo?: string;
}

export interface OrderCountRecord {
  mobile?: string;
  dateFrom?: string;
  dateTo?: string;
  totalCount: number;
  totalAmount: number;
  paidAmount: number;
  unpaidAmount: number;
  completedAmount: number;
  canceledAmount: number;
  consigneeMobileCount: number;
  memberMobileCount: number;
  buyerMobileCount: number;
  paidCount: number;
  unpaidCount: number;
  completedCount: number;
  canceledCount: number;
  latestOrderTime: string;
  amountField: string;
  source: string;
  evidence: string;
}

export interface ReadOnlyQueryResult {
  columns: string[];
  rows: DbRow[];
  executedSql: string;
  rowCount: number;
}

type DbRow = Record<string, unknown>;
type DbParam = string | number | boolean | null;

interface OrderCountPlan {
  includeMember: boolean;
  includeMemberAccount: boolean;
  source: string;
}

export class ReadOnlyDatabaseClient {
  constructor(private readonly getConfig: () => AppConfig) {}

  async findProductPrice(keyword: string): Promise<{ ok: boolean; message: string; record?: ProductPriceRecord }> {
    const config = this.getConfig().database;
    if (!config.enabled) {
      return { ok: false, message: "数据库只读查询未启用" };
    }
    if (!config.host || !config.username || !config.database) {
      return { ok: false, message: "数据库连接信息不完整" };
    }

    let connection: Connection | undefined;
    try {
      connection = await this.createConnection();
      await connection.query("SET SESSION TRANSACTION READ ONLY");
      await connection.query("SET SESSION MAX_EXECUTION_TIME=3000").catch(() => undefined);

      const searchKeyword = normalizeProductKeyword(keyword);
      const candidates = keywordCandidates(searchKeyword);
      const errors: string[] = [];
      const finders = [this.findDjkGoods.bind(this), this.findMallProduct.bind(this), this.findSpuOrSku.bind(this), this.findVideoProduct.bind(this)];
      for (const candidate of candidates) {
        for (const finder of finders) {
          try {
            const record = await finder(candidate, connection);
            if (record) {
              return { ok: true, message: "数据库只读查询命中", record };
            }
          } catch (error) {
            errors.push(error instanceof Error ? error.message : String(error));
          }
        }
      }
      return { ok: false, message: errors[0] ?? "数据库未匹配到商品" };
    } catch (error) {
      return { ok: false, message: error instanceof Error ? error.message : String(error) };
    } finally {
      await connection?.end();
    }
  }

  async findProductSummary(keyword: string): Promise<{ ok: boolean; message: string; record?: ProductSummaryRecord }> {
    const config = this.getConfig().database;
    if (!config.enabled) {
      return { ok: false, message: "数据库只读查询未启用" };
    }
    if (!config.host || !config.username || !config.database) {
      return { ok: false, message: "数据库连接信息不完整" };
    }

    let connection: Connection | undefined;
    try {
      connection = await this.createConnection();
      await connection.query("SET SESSION TRANSACTION READ ONLY");
      await connection.query("SET SESSION MAX_EXECUTION_TIME=3000").catch(() => undefined);

      const searchKeyword = normalizeProductKeyword(keyword);
      const candidates = keywordCandidates(searchKeyword);
      const errors: string[] = [];
      for (const candidate of candidates) {
        try {
          const record = await this.findMallProductSummary(candidate, connection);
          if (record) {
            return { ok: true, message: "商品资料只读查询命中", record };
          }
        } catch (error) {
          errors.push(error instanceof Error ? error.message : String(error));
        }
        try {
          const record = await this.findDjkGoodsSummary(candidate, connection);
          if (record) {
            return { ok: true, message: "商品资料只读查询命中", record };
          }
        } catch (error) {
          errors.push(error instanceof Error ? error.message : String(error));
        }
      }

      return { ok: false, message: errors[0] ?? "数据库未匹配到商品资料" };
    } catch (error) {
      return { ok: false, message: error instanceof Error ? error.message : String(error) };
    } finally {
      await connection?.end();
    }
  }

  async countOrders(params: OrderCountQuery): Promise<{ ok: boolean; message: string; record?: OrderCountRecord }> {
    const config = this.getConfig().database;
    if (!config.enabled) {
      return { ok: false, message: "数据库只读查询未启用" };
    }
    if (!config.host || !config.username || !config.database) {
      return { ok: false, message: "数据库连接信息不完整" };
    }

    const query: OrderCountQuery = {
      mobile: normalizeMobile(params.mobile),
      dateFrom: normalizeDateParam(params.dateFrom),
      dateTo: normalizeDateParam(params.dateTo)
    };
    if (query.dateFrom && query.dateTo && query.dateFrom >= query.dateTo) {
      return { ok: false, message: "日期范围不正确，结束日期必须晚于开始日期" };
    }

    let connection: Connection | undefined;
    const errors: string[] = [];
    try {
      connection = await this.createConnection();
      await connection.query("SET SESSION TRANSACTION READ ONLY");
      await connection.query("SET SESSION MAX_EXECUTION_TIME=3000").catch(() => undefined);

      const plans: OrderCountPlan[] = query.mobile
        ? [
            { includeMember: true, includeMemberAccount: true, source: "smqjh_oms.oms_order + smqjh_system.sm_member + smqjh_system.ums_member_account" },
            { includeMember: true, includeMemberAccount: false, source: "smqjh_oms.oms_order + smqjh_system.sm_member" },
            { includeMember: false, includeMemberAccount: false, source: "smqjh_oms.oms_order" }
          ]
        : [{ includeMember: false, includeMemberAccount: false, source: "smqjh_oms.oms_order" }];

      for (const plan of plans) {
        try {
          const record = await this.queryOrderCount(connection, query, plan);
          return { ok: true, message: "订单数量只读查询命中", record };
        } catch (error) {
          errors.push(error instanceof Error ? error.message : String(error));
        }
      }

      return { ok: false, message: errors[0] ?? "订单数量查询失败" };
    } catch (error) {
      return { ok: false, message: error instanceof Error ? error.message : String(error) };
    } finally {
      await connection?.end();
    }
  }

  async runReadOnlyQuery(sql: string): Promise<{ ok: boolean; message: string; result?: ReadOnlyQueryResult }> {
    const config = this.getConfig().database;
    if (!config.enabled) {
      return { ok: false, message: "数据库只读查询未启用" };
    }
    if (!config.host || !config.username || !config.database) {
      return { ok: false, message: "数据库连接信息不完整" };
    }

    const safeSql = normalizeSafeSelectSql(sql);
    try {
      assertReadOnlySql(safeSql);
      assertAllowedReadOnlyTables(safeSql);
    } catch (error) {
      return { ok: false, message: error instanceof Error ? error.message : String(error) };
    }

    let connection: Connection | undefined;
    try {
      connection = await this.createConnection();
      await connection.query("SET SESSION TRANSACTION READ ONLY");
      await connection.query("SET SESSION MAX_EXECUTION_TIME=3000").catch(() => undefined);
      const executedSql = withDefaultLimit(safeSql);
      const [rawRows, fields] = await connection.execute(executedSql);
      const rows = rawRows as DbRow[];
      const fieldColumns = Array.isArray(fields) ? fields.map((field) => String(field.name ?? "")).filter(Boolean) : [];
      const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
      return {
        ok: true,
        message: "数据库智能只读查询完成",
        result: {
          columns: columns.length > 0 ? columns : fieldColumns,
          rows,
          executedSql,
          rowCount: rows.length
        }
      };
    } catch (error) {
      return { ok: false, message: error instanceof Error ? error.message : String(error) };
    } finally {
      await connection?.end();
    }
  }

  private async queryOrderCount(connection: Connection, query: OrderCountQuery, plan: OrderCountPlan): Promise<OrderCountRecord> {
    const mobileLike = query.mobile ? `%${query.mobile}%` : "";
    const selectParams: DbParam[] = [];
    const selectParts = [
      "COUNT(*) AS totalCount",
      "COALESCE(SUM(t.orderAmount), 0) AS totalAmount",
      "COALESCE(SUM(CASE WHEN COALESCE(t.is_payed, 0) = 1 THEN t.orderAmount ELSE 0 END), 0) AS paidAmount",
      "COALESCE(SUM(CASE WHEN COALESCE(t.is_payed, 0) = 0 THEN t.orderAmount ELSE 0 END), 0) AS unpaidAmount",
      "COALESCE(SUM(CASE WHEN t.hb_order_status IN (80, 90, 100) THEN t.orderAmount ELSE 0 END), 0) AS completedAmount",
      "COALESCE(SUM(CASE WHEN t.hb_order_status IN (-1, 50, 60) THEN t.orderAmount ELSE 0 END), 0) AS canceledAmount",
      "SUM(CASE WHEN COALESCE(t.is_payed, 0) = 1 THEN 1 ELSE 0 END) AS paidCount",
      "SUM(CASE WHEN COALESCE(t.is_payed, 0) = 0 THEN 1 ELSE 0 END) AS unpaidCount",
      "SUM(CASE WHEN t.hb_order_status IN (80, 90, 100) THEN 1 ELSE 0 END) AS completedCount",
      "SUM(CASE WHEN t.hb_order_status IN (-1, 50, 60) THEN 1 ELSE 0 END) AS canceledCount",
      "MAX(t.create_time) AS latestOrderTime"
    ];

    if (query.mobile) {
      selectParts.push("SUM(t.consigneeMatched) AS consigneeMobileCount");
      selectParams.push(mobileLike);
      if (plan.includeMemberAccount) {
        selectParts.push("SUM(t.memberMatched) AS memberMobileCount");
        selectParams.push(mobileLike);
      } else if (plan.includeMember) {
        selectParts.push("SUM(t.memberMatched) AS memberMobileCount");
        selectParams.push(mobileLike);
      } else {
        selectParts.push("0 AS memberMobileCount");
      }

      if (plan.includeMember) {
        selectParts.push("SUM(t.buyerMatched) AS buyerMobileCount");
        selectParams.push(mobileLike);
      } else {
        selectParts.push("0 AS buyerMobileCount");
      }
    } else {
      selectParts.push("0 AS consigneeMobileCount", "0 AS memberMobileCount", "0 AS buyerMobileCount");
    }

    const joins: string[] = [];
    if (plan.includeMember) {
      joins.push("LEFT JOIN smqjh_system.sm_member b ON a.member_id = b.id");
    }
    if (plan.includeMemberAccount) {
      joins.push(
        "LEFT JOIN smqjh_system.ums_member_account ma ON ma.third_order_no = a.order_number AND ma.member_id = a.member_id AND COALESCE(ma.deleted, 0) = 0"
      );
    }

    const whereParts = ["COALESCE(a.delete_status, 0) = 0"];
    const whereParams: DbParam[] = [];
    if (query.mobile) {
      const mobileWhere = ["a.consignee_mobile LIKE ?"];
      whereParams.push(mobileLike);
      if (plan.includeMember) {
        mobileWhere.push("b.mobile LIKE ?");
        whereParams.push(mobileLike);
      }
      if (plan.includeMemberAccount) {
        mobileWhere.push("IFNULL(ma.mobile, b.mobile) LIKE ?");
        whereParams.push(mobileLike);
      }
      whereParts.push(`(${mobileWhere.join(" OR ")})`);
    }
    if (query.dateFrom) {
      whereParts.push("a.create_time >= ?");
      whereParams.push(toDateStart(query.dateFrom));
    }
    if (query.dateTo) {
      whereParts.push("a.create_time < ?");
      whereParams.push(toDateStart(query.dateTo));
    }

    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT
          ${selectParts.join(",\n          ")}
        FROM (
          SELECT DISTINCT
            a.order_id,
            a.is_payed,
            a.hb_order_status,
            a.create_time,
            COALESCE(a.actual_total, a.total, a.order_money, 0) AS orderAmount,
            ${query.mobile ? "CASE WHEN a.consignee_mobile LIKE ? THEN 1 ELSE 0 END" : "0"} AS consigneeMatched,
            ${
              query.mobile && plan.includeMemberAccount
                ? "CASE WHEN IFNULL(ma.mobile, b.mobile) LIKE ? THEN 1 ELSE 0 END"
                : query.mobile && plan.includeMember
                  ? "CASE WHEN b.mobile LIKE ? THEN 1 ELSE 0 END"
                  : "0"
            } AS memberMatched,
            ${query.mobile && plan.includeMember ? "CASE WHEN b.mobile LIKE ? THEN 1 ELSE 0 END" : "0"} AS buyerMatched
          FROM smqjh_oms.oms_order a
          ${joins.join("\n          ")}
          WHERE ${whereParts.join("\n            AND ")}
        ) t
      `,
      [...selectParams, ...whereParams]
    );
    const row = rows[0] ?? {};

    return {
      mobile: query.mobile,
      dateFrom: query.dateFrom,
      dateTo: query.dateTo,
      totalCount: numberValue(row.totalCount),
      totalAmount: numberValue(row.totalAmount),
      paidAmount: numberValue(row.paidAmount),
      unpaidAmount: numberValue(row.unpaidAmount),
      completedAmount: numberValue(row.completedAmount),
      canceledAmount: numberValue(row.canceledAmount),
      consigneeMobileCount: numberValue(row.consigneeMobileCount),
      memberMobileCount: numberValue(row.memberMobileCount),
      buyerMobileCount: numberValue(row.buyerMobileCount),
      paidCount: numberValue(row.paidCount),
      unpaidCount: numberValue(row.unpaidCount),
      completedCount: numberValue(row.completedCount),
      canceledCount: numberValue(row.canceledCount),
      latestOrderTime: formatDateValue(row.latestOrderTime),
      amountField: "COALESCE(actual_total, total, order_money)",
      source: plan.source,
      evidence: buildOrderCountEvidence(query, plan)
    };
  }

  private createConnection(): Promise<Connection> {
    const config = this.getConfig().database;
    return mysql.createConnection({
      host: config.host,
      port: config.port,
      database: config.database,
      user: config.username,
      password: config.password,
      connectTimeout: 8000,
      charset: "utf8mb4"
    });
  }

  private async query<T extends DbRow>(connection: Connection, sql: string, params: DbParam[]): Promise<T[]> {
    assertReadOnlySql(sql);
    const [rows] = await connection.execute(sql, params);
    return rows as T[];
  }

  private async findDjkGoods(keyword: string, connection: Connection): Promise<ProductPriceRecord | undefined> {
    const terms = keywordTerms(keyword);
    const termClause = terms.length > 0 ? terms.map(() => "goods_name LIKE ?").join(" AND ") : "1 = 0";
    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT id, goods_name, goods_code, price, shelf_status
        FROM pms_djk_goods
        WHERE goods_name LIKE ? OR (${termClause})
        ORDER BY shelf_status DESC, update_time DESC, id DESC
        LIMIT 1
      `,
      [`%${keyword}%`, ...terms.map((term) => `%${term}%`)]
    );
    const row = rows[0];
    if (!row) {
      return undefined;
    }

    return {
      source: "MySQL只读：pms_djk_goods",
      productName: stringValue(row.goods_name) || keyword,
      price: formatYuan(row.price),
      note: `大健康商品${stringValue(row.goods_code) ? `，编码：${stringValue(row.goods_code)}` : ""}${row.shelf_status !== undefined ? `，上下架：${row.shelf_status}` : ""}`,
      evidence: `只读 SELECT：pms_djk_goods.goods_name LIKE "%${keyword}%"`
    };
  }

  private async findSpuOrSku(keyword: string, connection: Connection): Promise<ProductPriceRecord | undefined> {
    const terms = keywordTerms(keyword);
    const termClause = terms.length > 0 ? terms.map(() => "(spu.name LIKE ? OR sku.name LIKE ? OR sku.sku_sn LIKE ?)").join(" AND ") : "1 = 0";
    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT
          spu.id AS spu_id,
          spu.name AS spu_name,
          spu.price AS spu_price,
          sku.id AS sku_id,
          sku.name AS sku_name,
          sku.sku_sn,
          sku.price AS sku_price
        FROM pms_spu spu
        LEFT JOIN pms_sku sku ON sku.spu_id = spu.id
        WHERE spu.name LIKE ? OR sku.name LIKE ? OR sku.sku_sn LIKE ? OR (${termClause})
        ORDER BY spu.update_time DESC, sku.id DESC
        LIMIT 1
      `,
      [
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        ...terms.flatMap((term) => [`%${term}%`, `%${term}%`, `%${term}%`])
      ]
    );
    const row = rows[0];
    if (!row) {
      return undefined;
    }

    const skuPrice = row.sku_price === null || row.sku_price === undefined ? undefined : row.sku_price;
    return {
      source: "MySQL只读：pms_spu/pms_sku",
      productName: [stringValue(row.spu_name), stringValue(row.sku_name)].filter(Boolean).join(" ") || keyword,
      price: formatCent(skuPrice ?? row.spu_price),
      note: `SPU ID：${stringValue(row.spu_id)}${row.sku_id ? `，SKU ID：${stringValue(row.sku_id)}` : ""}${row.sku_sn ? `，SKU：${stringValue(row.sku_sn)}` : ""}`,
      evidence: `只读 SELECT：pms_spu.name / pms_sku.name / pms_sku.sku_sn LIKE "%${keyword}%"`
    };
  }

  private async findMallProduct(keyword: string, connection: Connection): Promise<ProductPriceRecord | undefined> {
    const terms = keywordTerms(keyword);
    const termClause = terms.length > 0
      ? terms.map(() => "(p.prod_name LIKE ? OR p.brand_name LIKE ? OR s.prod_name LIKE ? OR s.sku_name LIKE ? OR s.sku_code LIKE ?)").join(" AND ")
      : "1 = 0";
    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT
          p.prod_id,
          p.prod_name,
          p.brand_name,
          p.price AS prod_price,
          p.ori_price,
          p.status AS prod_status,
          s.sku_id,
          s.sku_name,
          s.price AS sku_price,
          s.ori_price AS sku_ori_price,
          s.sku_code,
          s.status AS sku_status
        FROM tz_prod p
        LEFT JOIN tz_sku s ON s.prod_id = p.prod_id AND COALESCE(s.is_delete, 0) = 0
        WHERE COALESCE(p.is_delete, 0) = 0
          AND (
            p.prod_name LIKE ?
            OR p.brand_name LIKE ?
            OR s.prod_name LIKE ?
            OR s.sku_name LIKE ?
            OR s.sku_code LIKE ?
            OR (${termClause})
          )
        ORDER BY
          CASE
            WHEN p.prod_name LIKE ? OR s.prod_name LIKE ? OR s.sku_name LIKE ? THEN 0
            ELSE 1
          END,
          p.status DESC, s.status DESC, p.update_time DESC, p.prod_id DESC, s.sku_id DESC
        LIMIT 1
      `,
      [
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        ...terms.flatMap((term) => [`%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`]),
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`
      ]
    );
    const row = rows[0];
    if (!row) {
      return undefined;
    }

    const rawPrice = row.sku_price ?? row.sku_ori_price ?? row.prod_price ?? row.ori_price;
    const skuName = stringValue(row.sku_name);
    const prodName = stringValue(row.prod_name) || stringValue(row.sku_name) || keyword;
    return {
      source: "MySQL只读：tz_prod/tz_sku",
      productName: skuName && skuName !== prodName ? `${prodName} ${skuName}` : prodName,
      price: formatMallCent(rawPrice),
      note: `商城商品${stringValue(row.brand_name) ? `，品牌：${stringValue(row.brand_name)}` : ""}，prod_id：${stringValue(row.prod_id)}${row.sku_id ? `，SKU ID：${stringValue(row.sku_id)}` : ""}${rawPrice !== undefined && rawPrice !== null ? `，原始价格字段：${stringValue(rawPrice)}` : ""}`,
      evidence: `只读 SELECT：tz_prod.prod_name / tz_sku.sku_name LIKE "%${keyword}%"`
    };
  }

  private async findVideoProduct(keyword: string, connection: Connection): Promise<ProductPriceRecord | undefined> {
    const terms = keywordTerms(keyword);
    const termClause = terms.length > 0
      ? terms.map(() => "(pvp.product_name LIKE ? OR pvp.local_product_name LIKE ? OR pvp.product_number LIKE ?)").join(" AND ")
      : "1 = 0";
    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT
          pvp.id,
          pvp.product_name,
          pvp.local_product_name,
          pvp.product_number,
          pvp.product_status,
          COALESCE(MIN(pvcp.price), pvp.purchase_price, pvp.face_value) AS min_price,
          COALESCE(MAX(pvcp.price), pvp.purchase_price, pvp.face_value) AS max_price
        FROM pms_video_product pvp
        LEFT JOIN pms_video_channel_price pvcp ON pvcp.video_product_id = pvp.id
        WHERE pvp.product_name LIKE ?
           OR pvp.local_product_name LIKE ?
           OR pvp.product_number LIKE ?
           OR (${termClause})
        GROUP BY pvp.id, pvp.product_name, pvp.local_product_name, pvp.product_number, pvp.product_status
        ORDER BY pvp.update_time DESC, pvp.id DESC
        LIMIT 1
      `,
      [
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        ...terms.flatMap((term) => [`%${term}%`, `%${term}%`, `%${term}%`])
      ]
    );
    const row = rows[0];
    if (!row) {
      return undefined;
    }

    return {
      source: "MySQL只读：pms_video_product/pms_video_channel_price",
      productName: stringValue(row.local_product_name) || stringValue(row.product_name) || keyword,
      price: formatPriceRange(row.min_price, row.max_price),
      note: `虚拟/权益商品${stringValue(row.product_number) ? `，商品编号：${stringValue(row.product_number)}` : ""}${row.product_status !== undefined ? `，状态：${row.product_status}` : ""}`,
      evidence: `只读 SELECT：pms_video_product + pms_video_channel_price LIKE "%${keyword}%"`
    };
  }

  private async findMallProductSummary(keyword: string, connection: Connection): Promise<ProductSummaryRecord | undefined> {
    const terms = keywordTerms(keyword);
    const termClause = terms.length > 0
      ? terms.map(() => "(p.prod_name LIKE ? OR p.brand_name LIKE ? OR s.prod_name LIKE ? OR s.sku_name LIKE ? OR s.sku_code LIKE ?)").join(" AND ")
      : "1 = 0";
    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT
          p.prod_id,
          p.prod_name,
          p.brand_name,
          p.brief,
          p.price AS prod_price,
          p.ori_price,
          p.status AS prod_status,
          s.sku_id,
          s.sku_name,
          s.price AS sku_price,
          s.ori_price AS sku_ori_price,
          s.sku_code,
          s.status AS sku_status
        FROM tz_prod p
        LEFT JOIN tz_sku s ON s.prod_id = p.prod_id AND COALESCE(s.is_delete, 0) = 0
        WHERE COALESCE(p.is_delete, 0) = 0
          AND (
            p.prod_name LIKE ?
            OR p.brand_name LIKE ?
            OR s.prod_name LIKE ?
            OR s.sku_name LIKE ?
            OR s.sku_code LIKE ?
            OR (${termClause})
          )
        ORDER BY
          CASE
            WHEN p.prod_name LIKE ? OR s.prod_name LIKE ? OR s.sku_name LIKE ? THEN 0
            ELSE 1
          END,
          p.status DESC, s.status DESC, p.update_time DESC, p.prod_id DESC, s.sku_id DESC
        LIMIT 1
      `,
      [
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`,
        ...terms.flatMap((term) => [`%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`]),
        `%${keyword}%`,
        `%${keyword}%`,
        `%${keyword}%`
      ]
    );
    const row = rows[0];
    if (!row) {
      return undefined;
    }

    const rawPrice = row.sku_price ?? row.sku_ori_price ?? row.prod_price ?? row.ori_price;
    const skuName = stringValue(row.sku_name);
    const prodName = stringValue(row.prod_name) || skuName || keyword;
    const status = row.sku_status !== undefined && row.sku_status !== null ? stringValue(row.sku_status) : stringValue(row.prod_status);

    return {
      source: "MySQL只读：tz_prod/tz_sku",
      productName: skuName && skuName !== prodName ? `${prodName} ${skuName}` : prodName,
      brandName: stringValue(row.brand_name),
      price: formatMallCent(rawPrice),
      status: status || "未设置",
      description: stringValue(row.brief) || "商品描述字段 brief 为空",
      note: `商城商品，prod_id：${stringValue(row.prod_id)}${row.sku_id ? `，SKU ID：${stringValue(row.sku_id)}` : ""}${row.sku_code ? `，SKU编码：${stringValue(row.sku_code)}` : ""}`,
      evidence: `只读 SELECT：tz_prod.prod_name / tz_sku.sku_name LIKE "%${keyword}%"，COALESCE(is_delete, 0) = 0`
    };
  }

  private async findDjkGoodsSummary(keyword: string, connection: Connection): Promise<ProductSummaryRecord | undefined> {
    const terms = keywordTerms(keyword);
    const termClause = terms.length > 0 ? terms.map(() => "goods_name LIKE ?").join(" AND ") : "1 = 0";
    const rows = await this.query<DbRow>(
      connection,
      `
        SELECT id, goods_name, goods_code, price, shelf_status
        FROM pms_djk_goods
        WHERE goods_name LIKE ? OR (${termClause})
        ORDER BY shelf_status DESC, update_time DESC, id DESC
        LIMIT 1
      `,
      [`%${keyword}%`, ...terms.map((term) => `%${term}%`)]
    );
    const row = rows[0];
    if (!row) {
      return undefined;
    }

    return {
      source: "MySQL只读：pms_djk_goods",
      productName: stringValue(row.goods_name) || keyword,
      brandName: "",
      price: formatYuan(row.price),
      status: row.shelf_status === undefined ? "未设置" : stringValue(row.shelf_status),
      description: "大健康商品表暂未读取到独立商品描述字段",
      note: `大健康商品${stringValue(row.goods_code) ? `，编码：${stringValue(row.goods_code)}` : ""}`,
      evidence: `只读 SELECT：pms_djk_goods.goods_name LIKE "%${keyword}%"`
    };
  }
}

function assertReadOnlySql(sql: string): void {
  const normalized = sql.trim().toLowerCase();
  if (!normalized.startsWith("select")) {
    throw new Error("数据库只读通道仅允许 SELECT");
  }
  if (normalized.includes(";")) {
    throw new Error("数据库只读通道仅允许单条 SELECT");
  }
  if (/--|\/\*|\*\//.test(normalized)) {
    throw new Error("数据库只读通道拒绝包含 SQL 注释的语句");
  }
  if (/(^|[\s;])(insert|update|delete|drop|alter|truncate|create|replace|grant|revoke|call|set)\b/i.test(normalized)) {
    throw new Error("数据库只读通道拒绝写入或结构变更语句");
  }
}

function normalizeSafeSelectSql(sql: string): string {
  return sql
    .replace(/```sql/gi, "")
    .replace(/```/g, "")
    .trim()
    .replace(/;+$/g, "");
}

function withDefaultLimit(sql: string): string {
  if (/\blimit\s+\d+/i.test(sql)) {
    return sql;
  }
  return `${sql} LIMIT 100`;
}

function assertAllowedReadOnlyTables(sql: string): void {
  const allowedTables = new Set([
    "smqjh_oms.oms_order",
    "smqjh_oms.oms_order_item",
    "smqjh_system.sm_member",
    "smqjh_system.ums_member_account",
    "smqjh_pms.tz_prod",
    "smqjh_pms.tz_sku",
    "smqjh_pms.pms_djk_goods",
    "smqjh_pms.pms_spu",
    "smqjh_pms.pms_sku",
    "smqjh_pms.pms_video_product",
    "smqjh_pms.pms_video_channel_price",
    "oms_order",
    "oms_order_item",
    "sm_member",
    "ums_member_account",
    "tz_prod",
    "tz_sku",
    "pms_djk_goods",
    "pms_spu",
    "pms_sku",
    "pms_video_product",
    "pms_video_channel_price"
  ]);
  const referenced = Array.from(sql.matchAll(/\b(?:from|join)\s+([`.\w]+)/gi))
    .map((match) => match[1].replace(/`/g, "").toLowerCase());

  if (referenced.length === 0) {
    throw new Error("数据库智能查询必须包含 FROM 或 JOIN 表");
  }
  const rejected = referenced.filter((table) => !allowedTables.has(table));
  if (rejected.length > 0) {
    throw new Error(`数据库智能查询拒绝访问未授权表：${rejected.join("、")}`);
  }
  if (/\b(information_schema|mysql|performance_schema|sys)\./i.test(sql)) {
    throw new Error("数据库智能查询拒绝访问系统库");
  }
}

function keywordCandidates(keyword: string): string[] {
  const compact = normalizeProductKeyword(keyword);
  const compactNoSpace = compact.replace(/\s+/g, "");
  const withoutSpec = compact
    .replace(/单瓶|一瓶|1瓶|单件|一件|单个/g, " ")
    .replace(/\d+(?:\.\d+)?\s*(?:ml|mL|ML|毫升|l|L|升)(?:\s*[xX*×]\s*\d+\s*(?:瓶|桶|罐|支|盒)?)?/g, " ")
    .replace(/\s+/g, " ")
    .trim();
  const withoutSpecNoSpace = withoutSpec.replace(/\s+/g, "");
  return Array.from(new Set([compact, compactNoSpace, withoutSpec, withoutSpecNoSpace].filter(Boolean)));
}

function keywordTerms(keyword: string): string[] {
  const cleaned = normalizeProductKeyword(keyword);
  const segmenter = new Intl.Segmenter("zh-CN", { granularity: "word" });
  const segments = Array.from(segmenter.segment(cleaned))
    .map((segment) => segment.segment.trim())
    .filter((segment) => segment.length > 1 && !/^(单瓶|单个|同规格|价格|商品|饮用)$/.test(segment));
  const chineseBlocks = Array.from(cleaned.matchAll(/[\u4e00-\u9fa5]{2,}/g))
    .map((match) => match[0])
    .filter((block) => !/^(当前|商品|产品|价格|描述|说明|查询|查看|系统|后台)$/.test(block));
  const specs = cleaned.match(/\d+(?:\.\d+)?\s*(?:ml|mL|ML|毫升|l|L|升)(?:\s*[xX*×]\s*\d+\s*(?:瓶|桶|罐|支|盒)?)?/g) ?? [];
  return Array.from(new Set([...chineseBlocks, ...segments, ...specs.map((spec) => spec.replace(/\s+/g, ""))])).slice(0, 7);
}

function normalizeProductKeyword(keyword: string): string {
  return keyword
    .replace(/(?:和|与|跟|同|及|以及)?\s*(?:其它|其他|其余)?\s*(?:电商平台|电商|平台|淘宝|京东|拼多多|市面).*$/s, " ")
    .replace(/(?:帮我|麻烦|我要|我想|可以)?\s*(?:在|从)?\s*(?:业务系统|系统|后台|smqjh)(?:里面|里|中|的)?/gi, " ")
    .replace(/我方|我司|本方|己方|我们|咱们|我要|我想|帮我|麻烦|可以|一下|这个|某个|当前|只/g, " ")
    .replace(/商品描述|商品说明|商品资料|商品信息|描述|说明|资料|信息|简介|详情|是什么|还是什么/g, " ")
    .replace(/的?价格[呢吗]?|多少钱|多少|呢|吗|呀|商品|产品|(?:只)?(?:查询一下|查一下|查询|查看|查)|比价|对比一下|对比下|对比|比较一下|比较下|比较|做个|做一下|进行/g, " ")
    .replace(/的/g, " ")
    .replace(/[？?。！，,；;：:\n\r]/g, " ")
    .replace(/\s+/g, " ")
    .replace(/^(?:在|从|里|里面|中)\s+/g, "")
    .replace(/^(?:和|与|跟|同|及|以及)\s*/g, "")
    .replace(/\s*(?:和|与|跟|同|及|以及)$/g, "")
    .trim();
}

function formatYuan(value: unknown): string {
  const number = Number(value);
  return Number.isFinite(number) ? `¥${number.toFixed(2)}` : "未获取";
}

function formatCent(value: unknown): string {
  const number = Number(value);
  return Number.isFinite(number) ? `¥${(number / 100).toFixed(2)}` : "未获取";
}

function formatMallCent(value: unknown): string {
  const number = Number(value);
  return Number.isFinite(number) ? `¥${(number / 100).toFixed(2)}` : "未获取";
}

function formatPriceRange(min: unknown, max: unknown): string {
  const minNumber = Number(min);
  const maxNumber = Number(max);
  if (!Number.isFinite(minNumber) && !Number.isFinite(maxNumber)) {
    return "未设置渠道价";
  }
  if (!Number.isFinite(minNumber) && Number.isFinite(maxNumber)) {
    return `¥${maxNumber.toFixed(2)}`;
  }
  if (!Number.isFinite(maxNumber) || minNumber === maxNumber) {
    return `¥${minNumber.toFixed(2)}`;
  }
  return `¥${minNumber.toFixed(2)} - ¥${maxNumber.toFixed(2)}`;
}

function stringValue(value: unknown): string {
  return value === undefined || value === null ? "" : String(value);
}

function numberValue(value: unknown): number {
  const number = Number(value);
  return Number.isFinite(number) ? number : 0;
}

function normalizeMobile(value?: string): string | undefined {
  const match = (value ?? "").match(/1\d{10}/);
  return match?.[0];
}

function normalizeDateParam(value?: string): string | undefined {
  const match = (value ?? "").trim().match(/^(\d{4}-\d{2}-\d{2})/);
  return match?.[1];
}

function toDateStart(date: string): string {
  return `${date} 00:00:00`;
}

function formatDateValue(value: unknown): string {
  if (!value) {
    return "";
  }
  if (value instanceof Date) {
    return value.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", hour12: false });
  }
  return String(value);
}

function buildOrderCountEvidence(query: OrderCountQuery, plan: OrderCountPlan): string {
  const filters = ["COALESCE(smqjh_oms.oms_order.delete_status, 0) = 0"];
  if (query.mobile) {
    const mobileFields = ["oms_order.consignee_mobile"];
    if (plan.includeMember) {
      mobileFields.push("sm_member.mobile");
    }
    if (plan.includeMemberAccount) {
      mobileFields.push("ums_member_account.mobile");
    }
    filters.push(`${mobileFields.join(" / ")} LIKE "%${query.mobile}%"`);
  }
  if (query.dateFrom) {
    filters.push(`oms_order.create_time >= "${toDateStart(query.dateFrom)}"`);
  }
  if (query.dateTo) {
    filters.push(`oms_order.create_time < "${toDateStart(query.dateTo)}"`);
  }
  return `只读 SELECT：${plan.source}；金额字段：COALESCE(actual_total, total, order_money)；过滤条件：${filters.join("；")}`;
}
