// ============================================================
// 영업관리 > 구매요청서관리 / 품의서관리 — wace_plm 1:1
//
// wace 매핑:
//   /salesMng/purchaseRequestRegList.do
//     → salesMng.getSalesRequestMasterGridList (DOC_TYPE_FILTER='PURCHASE_REG')
//   /salesMng/purchaseRegProposalMngList.do
//     → salesMng.getPurchaseRegProposalMngGridList (DOC_TYPE='PURCHASE_REG_PROPOSAL')
//
// 데이터 소스(RPS 존재 테이블 기준):
//   ✓ sales_request_master, project_mgmt, contract_mgmt
//   ✓ comm_code, client_mng, supply_mng
//   ✓ mbom_header / mbom_detail / part_mng (품번/품명 fallback)
//   ✓ amaranth_approval (품의서 결재상태)
//   ✗ sales_request_part (운영DB 추출 후 신설 필요 — PART_NO fallback: MBOM_DETAIL)
// ============================================================

import { getPool } from "../database/db";
import { logger } from "../utils/logger";
import { createObjId } from "../utils/objidUtil";
import { AppError } from "../middleware/errorHandler";
import * as amaranth from "./amaranthApprovalClient";

export interface SalesPurchaseRequestFilter {
  project_no?: string;
  part_no?: string;
  part_name?: string;
  purchase_type?: string;
  writer?: string;
  part_type?: string;
  search_status?: string;
  proposal_no?: string;
  regdate_start?: string;
  regdate_end?: string;
  page?: number;
  page_size?: number;
}

interface ListResult<T> {
  rows: T[];
  totalCount: number;
  page: number;
  pageSize: number;
}

function clampPaging(f: SalesPurchaseRequestFilter) {
  const page = Math.max(1, Number(f.page ?? 1));
  const pageSize = Math.max(1, Math.min(500, Number(f.page_size ?? 50)));
  return { limit: pageSize, offset: (page - 1) * pageSize, page, pageSize };
}

// ─── 1) 구매요청서관리 ───────────────────────────────────────────
// wace: salesMng.getSalesRequestMasterGridList (DOC_TYPE_FILTER='PURCHASE_REG')
//
// 상태: 품의서생성/확정/작성중 (PURCHASE_REG_PROPOSAL 자식 존재 → '품의서생성')
//   wace CASE: PROPOSAL 타입 SRM.PROJECT_NO=SRM.OBJID 존재 시 '품의서생성'
//   RPS 보정: 동일 부모-자식 연결을 PURCHASE_REG_PROPOSAL.PROJECT_NO 로 추적
export async function listPurchaseRequestReg(
  filter: SalesPurchaseRequestFilter,
): Promise<ListResult<any>> {
  const pool = getPool();
  const { limit, offset, page, pageSize } = clampPaging(filter);

  const where: string[] = [`SRM.DOC_TYPE = 'PURCHASE_REG'`];
  const params: any[] = [];
  const p = (v: any) => { params.push(v); return `$${params.length}`; };

  if (filter.project_no)    where.push(`PM.PROJECT_NO ILIKE ${p(`%${filter.project_no}%`)}`);
  if (filter.purchase_type) where.push(`SRM.PURCHASE_TYPE = ${p(filter.purchase_type)}`);
  if (filter.writer) {
    const ph = p(filter.writer);
    where.push(`(SRM.REQUEST_USER_ID = ${ph} OR SRM.WRITER = ${ph})`);
  }
  if (filter.part_type)     where.push(`SRM.PRODUCT_NAME = ${p(filter.part_type)}`);
  if (filter.regdate_start) where.push(`SRM.REGDATE::DATE >= ${p(filter.regdate_start)}::DATE`);
  if (filter.regdate_end)   where.push(`SRM.REGDATE::DATE <= ${p(filter.regdate_end)}::DATE`);
  if (filter.part_no)       where.push(`EXISTS (SELECT 1 FROM MBOM_DETAIL MD JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR=PP.OBJID::VARCHAR WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID AND PP.PART_NO ILIKE ${p(`%${filter.part_no}%`)})`);
  if (filter.part_name)     where.push(`EXISTS (SELECT 1 FROM MBOM_DETAIL MD JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR=PP.OBJID::VARCHAR WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID AND PP.PART_NAME ILIKE ${p(`%${filter.part_name}%`)})`);

  const whereSql = `WHERE ${where.join(" AND ")}`;

  const dataSql = `
    SELECT
      SRM.OBJID                                       AS objid,
      SRM.REQUEST_MNG_NO                              AS request_mng_no,
      SRM.STATUS                                      AS status,
      SRM.PURCHASE_TYPE                               AS purchase_type,
      COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PURCHASE_TYPE LIMIT 1), '')
                                                      AS purchase_type_name,
      -- 주문유형: 프로젝트.CATEGORY_CD 우선
      COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE)        AS order_type,
      COALESCE(
        (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE) LIMIT 1),
        ''
      )                                               AS order_type_name,
      -- 제품구분: 프로젝트→계약 PRODUCT 우선
      COALESCE(
        (SELECT CC.CODE_NAME
           FROM CONTRACT_MGMT CM
           LEFT JOIN COMM_CODE CC ON CC.CODE_ID = CM.PRODUCT
          WHERE CM.OBJID = PM.CONTRACT_OBJID LIMIT 1),
        (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PRODUCT_NAME LIMIT 1),
        ''
      )                                               AS product_name_full,
      -- 고객사: 프로젝트→계약 CUSTOMER 우선
      COALESCE(
        (SELECT CASE WHEN CM.CUSTOMER_OBJID LIKE 'C_%'
                     THEN (SELECT CLIENT_NM FROM CLIENT_MNG WHERE 'C_' || OBJID::VARCHAR = CM.CUSTOMER_OBJID LIMIT 1)
                     ELSE (SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID::VARCHAR = CM.CUSTOMER_OBJID::VARCHAR LIMIT 1)
                END
           FROM CONTRACT_MGMT CM WHERE CM.OBJID = PM.CONTRACT_OBJID LIMIT 1),
        ''
      )                                               AS customer_name,
      -- 유/무상
      CASE COALESCE(
             (SELECT CM.PAID_TYPE FROM CONTRACT_MGMT CM WHERE CM.OBJID = PM.CONTRACT_OBJID LIMIT 1),
             SRM.PAID_TYPE)
        WHEN 'paid' THEN '유상'
        WHEN 'free' THEN '무상'
        ELSE ''
      END                                             AS paid_type_name,
      PM.PROJECT_NO                                   AS project_number,
      -- 품번/품명 (MBOM 우선)
      COALESCE((SELECT PP.PART_NO FROM MBOM_DETAIL MD
                 JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR = PP.OBJID::VARCHAR
                WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID
                ORDER BY MD.REGDATE LIMIT 1), '')     AS part_no,
      COALESCE((SELECT PP.PART_NAME FROM MBOM_DETAIL MD
                 JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR = PP.OBJID::VARCHAR
                WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID
                ORDER BY MD.REGDATE LIMIT 1), '')     AS part_name,
      GREATEST(
        (SELECT COUNT(DISTINCT PP.PART_NO)::int
           FROM MBOM_DETAIL MD
           JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR = PP.OBJID::VARCHAR
          WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID) - 1,
        0
      )                                               AS part_extra_count,
      -- 구매요청서 작성여부 (MBOM_HEADER 존재 시 Y)
      CASE WHEN SRM.MBOM_HEADER_OBJID IS NOT NULL THEN 'Y' ELSE 'N' END
                                                      AS has_purchase_request,
      -- 상태 라벨 (wace CASE 1:1)
      CASE
        WHEN EXISTS (
          SELECT 1 FROM SALES_REQUEST_MASTER P
           WHERE P.DOC_TYPE = 'PURCHASE_REG_PROPOSAL'
             AND P.PROJECT_NO = SRM.OBJID::VARCHAR
        ) THEN '품의서생성'
        WHEN SRM.STATUS = 'confirmed' THEN '확정'
        WHEN SRM.STATUS = 'create'    THEN '작성중'
        ELSE COALESCE(SRM.STATUS, '')
      END                                             AS status_title,
      COALESCE(SRM.REQUEST_USER_ID, SRM.WRITER)       AS request_user_id,
      COALESCE(user_name(COALESCE(SRM.REQUEST_USER_ID, SRM.WRITER)),
               COALESCE(SRM.REQUEST_USER_ID, SRM.WRITER), '')
                                                      AS request_user_name,
      REPLACE(COALESCE(SRM.DELIVERY_REQUEST_DATE, ''), '.', '-')
                                                      AS delivery_request_date,
      TO_CHAR(SRM.REGDATE, 'YYYY-MM-DD')              AS regdate_title,
      SRM.MBOM_HEADER_OBJID                           AS mbom_header_objid
    FROM SALES_REQUEST_MASTER SRM
    LEFT JOIN PROJECT_MGMT PM ON PM.OBJID::VARCHAR = SRM.PROJECT_NO
    ${whereSql}
    ORDER BY SRM.REGDATE DESC
    LIMIT ${p(limit)} OFFSET ${p(offset)}
  `;
  const countSql = `
    SELECT COUNT(*)::int AS cnt
      FROM SALES_REQUEST_MASTER SRM
      LEFT JOIN PROJECT_MGMT PM ON PM.OBJID::VARCHAR = SRM.PROJECT_NO
      ${whereSql}
  `;
  try {
    const [d, c] = await Promise.all([
      pool.query(dataSql, params),
      pool.query(countSql, params.slice(0, params.length - 2)),
    ]);
    return { rows: d.rows, totalCount: c.rows[0]?.cnt ?? 0, page, pageSize };
  } catch (e: any) {
    logger.error("listPurchaseRequestReg 실패", { error: e.message });
    return { rows: [], totalCount: 0, page, pageSize };
  }
}

// ─── 2) 영업관리 > 품의서관리 (구매요청서 → 품의서) ─────────────
// wace: salesMng.getPurchaseRegProposalMngGridList (DOC_TYPE='PURCHASE_REG_PROPOSAL')
//
// 결재상태 우선순위: AMR.STATUS > 내부 결재 > SRM.STATUS('create'→'등록중')
export async function listPurchaseRegProposal(
  filter: SalesPurchaseRequestFilter,
): Promise<ListResult<any>> {
  const pool = getPool();
  const { limit, offset, page, pageSize } = clampPaging(filter);

  const where: string[] = [
    `SRM.STATUS IN ('create','approvalRequest','approvalComplete','reject')`,
    `SRM.DOC_TYPE = 'PURCHASE_REG_PROPOSAL'`,
  ];
  const params: any[] = [];
  const p = (v: any) => { params.push(v); return `$${params.length}`; };

  if (filter.proposal_no)   where.push(`SRM.REQUEST_MNG_NO ILIKE ${p(`%${filter.proposal_no}%`)}`);
  if (filter.project_no)    where.push(`PM.PROJECT_NO ILIKE ${p(`%${filter.project_no}%`)}`);
  if (filter.purchase_type) where.push(`SRM.PURCHASE_TYPE = ${p(filter.purchase_type)}`);
  if (filter.writer)        where.push(`SRM.WRITER = ${p(filter.writer)}`);
  if (filter.part_type)     where.push(`SRM.PRODUCT_NAME = ${p(filter.part_type)}`);
  if (filter.regdate_start) where.push(`SRM.REGDATE::DATE >= ${p(filter.regdate_start)}::DATE`);
  if (filter.regdate_end)   where.push(`SRM.REGDATE::DATE <= ${p(filter.regdate_end)}::DATE`);
  if (filter.search_status) {
    // amaranth/내부 결재 종합 status = SEARCH_STATUS
    where.push(`(
      CASE
        WHEN AMR.STATUS = 'complete'  THEN 'approvalComplete'
        WHEN AMR.STATUS = 'inProcess' THEN 'inProcess'
        WHEN AMR.STATUS = 'reject'    THEN 'reject'
        ELSE SRM.STATUS
      END
    ) = ${p(filter.search_status)}`);
  }

  const whereSql = `WHERE ${where.join(" AND ")}`;

  const dataSql = `
    SELECT
      SRM.OBJID                                       AS objid,
      SRM.REQUEST_MNG_NO                              AS proposal_no,
      PM.PROJECT_NO                                   AS project_number,
      SRM.PURCHASE_TYPE                               AS purchase_type,
      COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PURCHASE_TYPE LIMIT 1), '')
                                                      AS purchase_type_name,
      COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE)        AS order_type,
      COALESCE(
        (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE) LIMIT 1),
        ''
      )                                               AS order_type_name,
      SRM.PRODUCT_NAME                                AS product_name,
      COALESCE(
        (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PRODUCT_NAME LIMIT 1), ''
      )                                               AS product_name_title,
      COALESCE((SELECT PP.PART_NO FROM MBOM_DETAIL MD
                 JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR = PP.OBJID::VARCHAR
                WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID
                ORDER BY MD.REGDATE LIMIT 1), '')     AS part_no,
      COALESCE((SELECT PP.PART_NAME FROM MBOM_DETAIL MD
                 JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR = PP.OBJID::VARCHAR
                WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID
                ORDER BY MD.REGDATE LIMIT 1), '')     AS part_name,
      GREATEST(
        (SELECT COUNT(DISTINCT PP.PART_NO)::int
           FROM MBOM_DETAIL MD
           JOIN PART_MNG PP ON MD.PART_OBJID::VARCHAR = PP.OBJID::VARCHAR
          WHERE MD.MBOM_HEADER_OBJID = SRM.MBOM_HEADER_OBJID) - 1,
        0
      )                                               AS part_extra_count,
      CASE
        WHEN AMR.STATUS = 'complete'  THEN 'approvalComplete'
        WHEN AMR.STATUS = 'inProcess' THEN 'inProcess'
        WHEN AMR.STATUS = 'reject'    THEN 'reject'
        ELSE SRM.STATUS
      END                                             AS status,
      CASE
        WHEN AMR.STATUS = 'complete'  THEN '결재완료'
        WHEN AMR.STATUS = 'inProcess' THEN '결재 상신중'
        WHEN AMR.STATUS = 'reject'    THEN '반려'
        ELSE '등록중'
      END                                             AS status_title,
      COALESCE(AMR.STATUS, '')                        AS amaranth_status,
      SRM.WRITER                                      AS writer,
      COALESCE(user_name(SRM.WRITER), SRM.WRITER, '') AS writer_name,
      TO_CHAR(SRM.REGDATE, 'YYYY-MM-DD')              AS regdate_title,
      SRM.MBOM_HEADER_OBJID                           AS mbom_header_objid
    FROM SALES_REQUEST_MASTER SRM
    LEFT JOIN PROJECT_MGMT PM ON PM.OBJID::VARCHAR = SRM.PROJECT_NO
    LEFT JOIN AMARANTH_APPROVAL AMR
           ON SRM.OBJID::VARCHAR = AMR.TARGET_OBJID
          AND AMR.TARGET_TYPE = 'PROPOSAL'
    ${whereSql}
    ORDER BY SRM.REGDATE DESC
    LIMIT ${p(limit)} OFFSET ${p(offset)}
  `;
  const countSql = `
    SELECT COUNT(*)::int AS cnt
      FROM SALES_REQUEST_MASTER SRM
      LEFT JOIN PROJECT_MGMT PM ON PM.OBJID::VARCHAR = SRM.PROJECT_NO
      LEFT JOIN AMARANTH_APPROVAL AMR
             ON SRM.OBJID::VARCHAR = AMR.TARGET_OBJID
            AND AMR.TARGET_TYPE = 'PROPOSAL'
      ${whereSql}
  `;
  try {
    const [d, c] = await Promise.all([
      pool.query(dataSql, params),
      pool.query(countSql, params.slice(0, params.length - 2)),
    ]);
    return { rows: d.rows, totalCount: c.rows[0]?.cnt ?? 0, page, pageSize };
  } catch (e: any) {
    logger.error("listPurchaseRegProposal 실패", { error: e.message });
    return { rows: [], totalCount: 0, page, pageSize };
  }
}

// ─── 3) 구매요청서 단건 + 라인 조회 ─────────────────────────────
// wace: getSalesRequestMasterInfo + getSalesRequestTargetList
export async function getPurchaseRequestDetail(srmObjid: string) {
  const pool = getPool();
  const headRes = await pool.query(
    `SELECT
       SRM.OBJID, SRM.REQUEST_MNG_NO, SRM.PROJECT_NO, SRM.MBOM_HEADER_OBJID,
       SRM.PURCHASE_TYPE, SRM.ORDER_TYPE, SRM.PRODUCT_NAME, SRM.AREA_CD,
       SRM.CUSTOMER_OBJID, SRM.PAID_TYPE, SRM.DELIVERY_REQUEST_DATE,
       SRM.REQUEST_USER_ID, SRM.WRITER, SRM.STATUS, SRM.DOC_TYPE,
       PM.PROJECT_NO AS PROJECT_NUMBER, PM.PROJECT_NAME,
       PM.CATEGORY_CD, PM.CONTRACT_OBJID
       FROM SALES_REQUEST_MASTER SRM
       LEFT JOIN PROJECT_MGMT PM ON PM.OBJID::VARCHAR = SRM.PROJECT_NO
      WHERE SRM.OBJID = $1`,
    [srmObjid],
  );
  if (headRes.rowCount === 0) throw new AppError("구매요청서를 찾을 수 없습니다.", 404);
  const partRes = await pool.query(
    `SELECT SRP.OBJID, SRP.PART_OBJID,
            COALESCE(PM.PART_NO, '')   AS PART_NO,
            COALESCE(PM.PART_NAME, '') AS PART_NAME,
            COALESCE(FLOOR(NULLIF(SRP.QTY, '')::NUMERIC)::INTEGER, 0)::TEXT AS QTY,
            SRP.ORG_QTY, SRP.PARTNER_OBJID,
            COALESCE(NULLIF(SRP.PARTNER_PRICE, '')::NUMERIC, 0)::NUMERIC(18,2) AS PARTNER_PRICE,
            COALESCE(SRP.UNIT_PRICE, 0)::NUMERIC(18,2) AS UNIT_PRICE,
            COALESCE(SRP.VENDOR_PM, '') AS VENDOR_PM,
            SRP.DELIVERY_REQUEST_DATE, SRP.STATUS, SRP.PROPOSAL_DATE
       FROM SALES_REQUEST_PART SRP
       LEFT JOIN PART_MNG PM ON PM.OBJID::VARCHAR = SRP.PART_OBJID::VARCHAR
      WHERE SRP.SALES_REQUEST_MASTER_OBJID = $1
      ORDER BY SRP.REGDATE`,
    [srmObjid],
  );
  return { header: headRes.rows[0], parts: partRes.rows };
}

// ─── 3-0) 공급업체 옵션 — client_mng 기반 ──────────────────────
// wace partMng 의 fnc_getClientMngListAppend 와 동일.
//   M-BOM.vendor / sales_request_part.partner_objid 는 'C_' prefix 없이 client_mng.OBJID 직접 저장.
//   → 옵션 코드는 OBJID 그대로(접두 X). 기존 listSupplierOptions(supply_mng)는 다른 메뉴 호환용으로 유지.
export async function listVendorOptions(): Promise<{ code: string; label: string }[]> {
  const pool = getPool();
  try {
    const r = await pool.query(
      `SELECT OBJID::VARCHAR AS code, CLIENT_NM AS label
         FROM CLIENT_MNG
        WHERE COALESCE(USE_YN, '1') IN ('1', 'Y', 'y')
          AND CLIENT_NM IS NOT NULL AND CLIENT_NM <> ''
        ORDER BY CLIENT_NM`,
    );
    return r.rows;
  } catch (e: any) {
    logger.error("listVendorOptions 실패", { error: e.message });
    return [];
  }
}

// ─── 3-1) 프로젝트 자동채움 정보 (wace purchaseOrderAdminSupplyInfo 1:1) ─
//   프로젝트 선택 시 주문유형(CATEGORY_CD) · 제품구분(PRODUCT) · 국내/해외(AREA_CD) ·
//   고객사(CUSTOMER_OBJID) · 유/무상(PAID_TYPE) 자동 채움 + M-BOM 헤더.
export async function getProjectAutoFillInfo(projectObjid: string) {
  const pool = getPool();
  const sql = `
    SELECT
      PM.OBJID,
      PM.PROJECT_NO,
      PM.PROJECT_NAME,
      PM.CATEGORY_CD,
      (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = PM.CATEGORY_CD LIMIT 1) AS CATEGORY_NAME,
      PM.CUSTOMER_OBJID,
      CASE
        WHEN PM.CUSTOMER_OBJID LIKE 'C_%'
          THEN (SELECT CLIENT_NM FROM CLIENT_MNG WHERE 'C_' || OBJID::VARCHAR = PM.CUSTOMER_OBJID LIMIT 1)
        ELSE (SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID::VARCHAR = PM.CUSTOMER_OBJID::VARCHAR LIMIT 1)
      END                                                                       AS CUSTOMER_NAME,
      PM.PRODUCT,
      (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = PM.PRODUCT LIMIT 1)       AS PRODUCT_NAME,
      PM.AREA_CD,
      (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = PM.AREA_CD LIMIT 1)       AS AREA_NAME,
      CM.PAID_TYPE,
      CM.OBJID                                                                  AS CONTRACT_OBJID,
      (SELECT MH.OBJID FROM MBOM_HEADER MH
        WHERE MH.PROJECT_OBJID::VARCHAR = PM.OBJID::VARCHAR
        ORDER BY MH.REGDATE DESC LIMIT 1)                                       AS MBOM_HEADER_OBJID
    FROM PROJECT_MGMT PM
    LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID::VARCHAR
    WHERE PM.OBJID::VARCHAR = $1
    LIMIT 1
  `;
  try {
    const r = await pool.query(sql, [projectObjid]);
    return r.rows[0] || null;
  } catch (e: any) {
    logger.error("getProjectAutoFillInfo 실패", { error: e.message, projectObjid });
    return null;
  }
}

// ─── 4) 프로젝트별 M-BOM 품목 (구매요청서 신규 작성용) ─────────
// wace: salesMng.SalesBomPartListByProjectUnit (mbom_detail → part_mng)
export async function listMbomPartsForProject(projectObjid: string) {
  const pool = getPool();
  const sql = `
    SELECT
      MD.OBJID                                AS mbom_detail_objid,
      MD.PART_OBJID                           AS part_objid,
      MH.OBJID                                AS mbom_header_objid,
      COALESCE(PM.PART_NO, '')                AS part_no,
      COALESCE(PM.PART_NAME, '')              AS part_name,
      COALESCE(MD.UNIT, '')                   AS unit,
      COALESCE(FLOOR(COALESCE(MD.PRODUCTION_QTY, MD.PO_QTY, 0))::INTEGER, 0) AS qty,
      COALESCE(MD.UNIT_PRICE, 0)::NUMERIC(18,2) AS unit_price,
      COALESCE(MD.VENDOR, '')                 AS vendor_objid,
      CASE
        WHEN MD.VENDOR IS NULL OR MD.VENDOR = '' THEN ''
        WHEN MD.VENDOR LIKE 'C_%'
          THEN (SELECT CLIENT_NM FROM CLIENT_MNG WHERE 'C_' || OBJID::VARCHAR = MD.VENDOR LIMIT 1)
        ELSE (SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.VENDOR LIMIT 1)
      END                                     AS vendor_name
    FROM MBOM_HEADER MH
    JOIN MBOM_DETAIL MD ON MD.MBOM_HEADER_OBJID = MH.OBJID
    LEFT JOIN PART_MNG PM ON PM.OBJID::VARCHAR = MD.PART_OBJID::VARCHAR
    WHERE MH.PROJECT_OBJID::VARCHAR = $1
      AND COALESCE(MD.USE_YN, 'Y') = 'Y'
    ORDER BY MD.REGDATE
  `;
  try {
    const r = await pool.query(sql, [projectObjid]);
    return r.rows;
  } catch (e: any) {
    logger.error("listMbomPartsForProject 실패", { error: e.message, projectObjid });
    return [];
  }
}

// ─── 5) 구매요청서 저장 (UPSERT master + 라인 재생성) ────────────
// wace: saveSalesRequestInfo → mergeSalesRequestMasterInfo + initSalesRequestPart + mergeSalesRequestPartInfo
//   doc_type='PURCHASE_REG' 명시 (구매요청서관리 신규 메뉴 흐름).
//   request_mng_no 채번: R + YYYYMMDD + - + 3자리 (wace mergeSalesRequestMasterInfo 1:1)
export interface SavePurchaseRequestPayload {
  objid?: string;
  project_no?: string;
  mbom_header_objid?: string;
  purchase_type?: string;
  order_type?: string;
  product_name?: string;
  area_cd?: string;
  customer_objid?: string;
  paid_type?: string;
  delivery_request_date?: string;
  parts: Array<{
    objid?: string;
    part_objid: string;
    part_name?: string;
    qty?: string | number;
    org_qty?: string | number;
    partner_objid?: string;
    partner_price?: string | number;
    delivery_request_date?: string;
    status?: string;
  }>;
}
export async function savePurchaseRequest(userId: string, payload: SavePurchaseRequestPayload) {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    let srmObjid = payload.objid;
    const isNew = !srmObjid;
    if (isNew) srmObjid = createObjId();

    // 채번 — wace mergeSalesRequestMasterInfo 의 SELECT 절 1:1
    const nextNoSql = `
      SELECT 'R'||TO_CHAR(NOW(),'YYYYMMDD')||'-'||
             LPAD((COALESCE(MAX(SUBSTR(REQUEST_MNG_NO,11,13)),'0')::INTEGER+1)::TEXT,3,'0') AS no
        FROM SALES_REQUEST_MASTER
       WHERE DOC_TYPE IN ('PURCHASE_REQUEST','PURCHASE_REG') OR DOC_TYPE IS NULL
    `;
    let requestMngNo: string | null = null;
    if (isNew) {
      const noRes = await client.query(nextNoSql);
      requestMngNo = noRes.rows[0]?.no ?? null;
    }

    if (isNew) {
      await client.query(
        `INSERT INTO SALES_REQUEST_MASTER
           (OBJID, REQUEST_MNG_NO, PROJECT_NO, MBOM_HEADER_OBJID,
            PURCHASE_TYPE, ORDER_TYPE, PRODUCT_NAME, AREA_CD,
            CUSTOMER_OBJID, PAID_TYPE, DELIVERY_REQUEST_DATE,
            REQUEST_USER_ID, STATUS, WRITER, REGDATE, DOC_TYPE)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,'create',$12,NOW(),'PURCHASE_REG')`,
        [
          srmObjid, requestMngNo, payload.project_no, payload.mbom_header_objid,
          payload.purchase_type, payload.order_type, payload.product_name, payload.area_cd,
          payload.customer_objid, payload.paid_type, payload.delivery_request_date || null,
          userId,
        ],
      );
    } else {
      await client.query(
        `UPDATE SALES_REQUEST_MASTER
            SET PROJECT_NO=$2, MBOM_HEADER_OBJID=$3,
                PURCHASE_TYPE=$4, ORDER_TYPE=$5, PRODUCT_NAME=$6, AREA_CD=$7,
                CUSTOMER_OBJID=$8, PAID_TYPE=$9, DELIVERY_REQUEST_DATE=$10
          WHERE OBJID=$1`,
        [
          srmObjid, payload.project_no, payload.mbom_header_objid,
          payload.purchase_type, payload.order_type, payload.product_name, payload.area_cd,
          payload.customer_objid, payload.paid_type, payload.delivery_request_date || null,
        ],
      );
    }

    // 라인 재생성 (wace initSalesRequestPart + mergeSalesRequestPartInfo)
    await client.query(`DELETE FROM SALES_REQUEST_PART WHERE SALES_REQUEST_MASTER_OBJID=$1`, [srmObjid]);

    for (const p of payload.parts || []) {
      const partObjid = p.objid || createObjId();
      const qtyVal = p.qty == null ? "0" : String(p.qty);
      const orgQtyVal = p.org_qty == null ? "0" : String(p.org_qty);
      await client.query(
        `INSERT INTO SALES_REQUEST_PART
           (OBJID, SALES_REQUEST_MASTER_OBJID, PART_OBJID, PART_NAME,
            QTY, ORG_QTY, PARTNER_OBJID, PARTNER_PRICE,
            DELIVERY_REQUEST_DATE, WRITER, REGDATE, STATUS)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,NOW(),$11)`,
        [
          partObjid, srmObjid, p.part_objid, p.part_name || null,
          qtyVal, orgQtyVal, p.partner_objid || null,
          p.partner_price == null ? null : String(p.partner_price),
          p.delivery_request_date || null,
          userId, p.status || "create",
        ],
      );
    }

    await client.query("COMMIT");
    return { objid: srmObjid, request_mng_no: requestMngNo, isNew };
  } catch (e: any) {
    await client.query("ROLLBACK");
    logger.error("savePurchaseRequest 실패", { error: e.message });
    throw e;
  } finally {
    client.release();
  }
}

// ─── 6) 품의서생성 대상 품목 조회 ──────────────────────────────
// wace: getProposalTargetPartsFromPurchaseReg
//   조건: 단가(UNIT_PRICE 또는 PARTNER_PRICE) > 0 AND 공급업체(VENDOR_PM 또는 PARTNER_OBJID) 입력 AND PROPOSAL_DATE 미입력
export async function getProposalTargetParts(srmObjid: string) {
  const pool = getPool();
  const targetSql = `
    SELECT
      SRP.OBJID, SRP.PART_OBJID,
      COALESCE(PM.PART_NO, '')   AS PART_NO,
      COALESCE(PM.PART_NAME, '') AS PART_NAME,
      COALESCE(FLOOR(NULLIF(SRP.QTY, '')::NUMERIC)::INTEGER, 0)::TEXT AS QTY,
      COALESCE(SRP.UNIT_PRICE, NULLIF(SRP.PARTNER_PRICE,'')::NUMERIC, 0)::NUMERIC(18,2) AS UNIT_PRICE,
      SRP.TOTAL_PRICE::NUMERIC(18,2) AS TOTAL_PRICE,
      COALESCE(SRP.VENDOR_PM, SRP.PARTNER_OBJID, '') AS VENDOR_PM,
      CASE
        WHEN COALESCE(SRP.VENDOR_PM, SRP.PARTNER_OBJID) IS NULL THEN ''
        WHEN COALESCE(SRP.VENDOR_PM, SRP.PARTNER_OBJID) LIKE 'C_%'
          THEN (SELECT CLIENT_NM FROM CLIENT_MNG WHERE 'C_' || OBJID::VARCHAR = COALESCE(SRP.VENDOR_PM, SRP.PARTNER_OBJID) LIMIT 1)
        ELSE (SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = COALESCE(SRP.VENDOR_PM, SRP.PARTNER_OBJID) LIMIT 1)
      END AS VENDOR_NAME
    FROM SALES_REQUEST_PART SRP
    LEFT JOIN PART_MNG PM ON SRP.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
    WHERE SRP.SALES_REQUEST_MASTER_OBJID = $1
      AND (
        (SRP.UNIT_PRICE IS NOT NULL AND SRP.UNIT_PRICE > 0)
        OR (SRP.PARTNER_PRICE IS NOT NULL AND SRP.PARTNER_PRICE <> '' AND SRP.PARTNER_PRICE::NUMERIC > 0)
      )
      AND (
        (SRP.VENDOR_PM     IS NOT NULL AND SRP.VENDOR_PM     <> '')
        OR (SRP.PARTNER_OBJID IS NOT NULL AND SRP.PARTNER_OBJID <> '')
      )
      AND SRP.PROPOSAL_DATE IS NULL
    ORDER BY SRP.REGDATE
  `;
  const excludedSql = `
    SELECT
      SRP.OBJID, SRP.PART_OBJID,
      COALESCE(PM.PART_NO,'')   AS PART_NO,
      COALESCE(PM.PART_NAME,'') AS PART_NAME,
      COALESCE(FLOOR(NULLIF(SRP.QTY, '')::NUMERIC)::INTEGER, 0)::TEXT AS QTY
    FROM SALES_REQUEST_PART SRP
    LEFT JOIN PART_MNG PM ON SRP.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
    WHERE SRP.SALES_REQUEST_MASTER_OBJID = $1
      AND (
        (SRP.UNIT_PRICE IS NOT NULL AND SRP.UNIT_PRICE > 0)
        OR (SRP.PARTNER_PRICE IS NOT NULL AND SRP.PARTNER_PRICE <> '' AND SRP.PARTNER_PRICE::NUMERIC > 0)
      )
      AND (SRP.VENDOR_PM     IS NULL OR SRP.VENDOR_PM     = '')
      AND (SRP.PARTNER_OBJID IS NULL OR SRP.PARTNER_OBJID = '')
      AND SRP.PROPOSAL_DATE IS NULL
    ORDER BY SRP.REGDATE
  `;
  const [t, x] = await Promise.all([pool.query(targetSql, [srmObjid]), pool.query(excludedSql, [srmObjid])]);
  return { targets: t.rows, excluded: x.rows };
}

// ─── 7) 품의서 생성 (PURCHASE_REG → PURCHASE_REG_PROPOSAL) ─────
// wace: createProposalFromPurchaseReg
//   1) request_mng_no 채번 (P + YYYYMMDD + - + 3자리)
//   2) sales_request_master INSERT (DOC_TYPE='PURCHASE_REG_PROPOSAL', PROJECT_NO=원본 OBJID, STATUS='create')
//   3) 선택된 SRP 행을 새 master 로 복사 (UNIT_PRICE/VENDOR_PM 보정, PROPOSAL_DATE=NOW)
//   4) 원본 SRP.PROPOSAL_DATE = NOW (재생성 방지)
export async function createProposalFromPurchaseReg(userId: string, srmObjid: string) {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");

    // 1) 원본 master 정보 조회
    const masterRes = await client.query(
      `SELECT OBJID, PROJECT_NO, PURCHASE_TYPE, ORDER_TYPE, PRODUCT_NAME, AREA_CD,
              CUSTOMER_OBJID, PAID_TYPE, DELIVERY_REQUEST_DATE, MBOM_HEADER_OBJID, DOC_TYPE
         FROM SALES_REQUEST_MASTER WHERE OBJID=$1`,
      [srmObjid],
    );
    if (masterRes.rowCount === 0) throw new AppError("구매요청서를 찾을 수 없습니다.", 404);
    const m = masterRes.rows[0];
    if (m.doc_type && m.doc_type !== "PURCHASE_REG" && m.doc_type !== "PURCHASE_REQUEST") {
      throw new AppError("구매요청서만 품의서를 생성할 수 있습니다.", 400);
    }

    // 2) 대상 품목 조회 (위 getProposalTargetParts 와 동일 SQL — 트랜잭션 내 client 재사용)
    const partsRes = await client.query(
      `SELECT SRP.OBJID, SRP.PART_OBJID, SRP.QTY, SRP.UNIT_PRICE, SRP.TOTAL_PRICE,
              SRP.PARTNER_PRICE, SRP.VENDOR_PM, SRP.PARTNER_OBJID, SRP.NET_QTY, SRP.USE_YN
         FROM SALES_REQUEST_PART SRP
        WHERE SRP.SALES_REQUEST_MASTER_OBJID = $1
          AND (
            (SRP.UNIT_PRICE IS NOT NULL AND SRP.UNIT_PRICE > 0)
            OR (SRP.PARTNER_PRICE IS NOT NULL AND SRP.PARTNER_PRICE <> '' AND SRP.PARTNER_PRICE::NUMERIC > 0)
          )
          AND (
            (SRP.VENDOR_PM     IS NOT NULL AND SRP.VENDOR_PM     <> '')
            OR (SRP.PARTNER_OBJID IS NOT NULL AND SRP.PARTNER_OBJID <> '')
          )
          AND SRP.PROPOSAL_DATE IS NULL
        ORDER BY SRP.REGDATE`,
      [srmObjid],
    );
    if (partsRes.rowCount === 0) {
      throw new AppError("품의서 생성 대상 품목이 없습니다. (단가+공급업체 입력 + 품의서 미생성 품목만 대상)", 400);
    }

    // 3) 품의서 채번
    const proposalNoRes = await client.query(
      `SELECT 'P'||TO_CHAR(NOW(),'YYYYMMDD')||'-'||
              LPAD((COALESCE(MAX(SUBSTR(REQUEST_MNG_NO,11,3))::INTEGER, 0)+1)::TEXT,3,'0') AS no
         FROM SALES_REQUEST_MASTER
        WHERE DOC_TYPE IN ('PROPOSAL','PURCHASE_REG_PROPOSAL')
          AND REQUEST_MNG_NO LIKE 'P'||TO_CHAR(NOW(),'YYYYMMDD')||'%'`,
    );
    const proposalNo = proposalNoRes.rows[0]?.no;

    // 4) 품의서 master INSERT (PROJECT_NO=원본 OBJID — 자식 관계 추적)
    const proposalObjid = createObjId();
    await client.query(
      `INSERT INTO SALES_REQUEST_MASTER
         (OBJID, REQUEST_MNG_NO, PROJECT_NO, PURCHASE_TYPE, ORDER_TYPE, PRODUCT_NAME,
          AREA_CD, CUSTOMER_OBJID, PAID_TYPE, REQUEST_USER_ID, DELIVERY_REQUEST_DATE,
          STATUS, WRITER, REGDATE, DOC_TYPE)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,'create',$10,NOW(),'PURCHASE_REG_PROPOSAL')`,
      [
        proposalObjid, proposalNo, m.objid,
        m.purchase_type, m.order_type, m.product_name,
        m.area_cd, m.customer_objid, m.paid_type, userId,
        m.delivery_request_date,
      ],
    );

    // 5) 품의서 part INSERT (원본 SRP 복사, PROPOSAL_DATE=NOW)
    const sourceObjids: string[] = [];
    for (const p of partsRes.rows) {
      sourceObjids.push(p.objid);
      const newPartObjid = createObjId();
      await client.query(
        `INSERT INTO SALES_REQUEST_PART
           (OBJID, SALES_REQUEST_MASTER_OBJID, PART_OBJID, QTY, UNIT_PRICE, TOTAL_PRICE,
            VENDOR_PM, NET_QTY, PO_QTY, USE_YN, PROPOSAL_DATE, WRITER, REGDATE)
         VALUES ($1, $2, $3, $4,
           CASE
             WHEN $5::NUMERIC IS NOT NULL AND $5::NUMERIC > 0 THEN $5::NUMERIC
             WHEN $6 IS NOT NULL AND $6 <> ''                 THEN $6::NUMERIC
             ELSE 0
           END,
           CASE
             WHEN $7::NUMERIC IS NOT NULL AND $7::NUMERIC > 0 THEN $7::NUMERIC
             ELSE COALESCE(NULLIF($4,'')::NUMERIC,0) *
                  CASE
                    WHEN $5::NUMERIC IS NOT NULL AND $5::NUMERIC > 0 THEN $5::NUMERIC
                    WHEN $6 IS NOT NULL AND $6 <> ''                 THEN $6::NUMERIC
                    ELSE 0
                  END
           END,
           COALESCE(NULLIF($8,''), $9),
           $10,
           COALESCE(NULLIF($4,'')::NUMERIC, 0),
           $11,
           NOW(), $12, NOW())`,
        [
          newPartObjid, proposalObjid, p.part_objid, p.qty,
          p.unit_price, p.partner_price, p.total_price,
          p.vendor_pm, p.partner_objid, p.net_qty,
          p.use_yn || "Y", userId,
        ],
      );
    }

    // 6) 원본 SRP.PROPOSAL_DATE 업데이트 (재생성 방지)
    await client.query(
      `UPDATE SALES_REQUEST_PART SET PROPOSAL_DATE = NOW() WHERE OBJID = ANY($1::varchar[])`,
      [sourceObjids],
    );

    await client.query("COMMIT");
    logger.info("품의서 생성", { srmObjid, proposalObjid, proposalNo, partCount: sourceObjids.length });
    return { proposal_objid: proposalObjid, proposal_no: proposalNo, part_count: sourceObjids.length };
  } catch (e: any) {
    await client.query("ROLLBACK");
    if (e instanceof AppError) throw e;
    logger.error("createProposalFromPurchaseReg 실패", { error: e.message });
    throw new AppError(`품의서 생성 실패: ${e.message}`, 500);
  } finally {
    client.release();
  }
}

// ─── 8) 품의서 결재상신 (Amaranth SSO) ──────────────────────────
// wace: purchaseRegProposalMngList.jsp:75~99 fn_openAmaranthApproval + ApprovalService.getAmaranthSsoUrl
//   target_type='PROPOSAL', formId='1163', compSeq='1000'
//   재상신/재사용 로직은 G11(견적) 동일 패턴.
export async function startProposalApproval(
  userId: string,
  proposalSrmObjid: string,
  opts: { approvalTitle?: string; subjectStr?: string } = {},
): Promise<{ fullUrl: string; approKey: string; status: string; proposalObjid: string }> {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");

    const userRes = await client.query(
      `SELECT user_id, user_name, emp_seq FROM user_info WHERE user_id=$1 LIMIT 1`,
      [userId],
    );
    const u = userRes.rows[0];
    if (!u) throw new AppError("사용자 정보를 찾을 수 없습니다.", 401);
    const empSeq = String(u.emp_seq ?? "").trim();
    if (!empSeq) throw new AppError(`empSeq 정보가 없습니다. (userId: ${userId}) 관리자에게 문의하세요.`, 400);

    const headRes = await client.query(
      `SELECT OBJID, REQUEST_MNG_NO, DOC_TYPE, STATUS
         FROM SALES_REQUEST_MASTER WHERE OBJID=$1`,
      [proposalSrmObjid],
    );
    if (headRes.rowCount === 0) throw new AppError("품의서를 찾을 수 없습니다.", 404);
    const head = headRes.rows[0];
    if (head.doc_type !== "PURCHASE_REG_PROPOSAL" && head.doc_type !== "PROPOSAL") {
      throw new AppError("품의서만 결재상신할 수 있습니다.", 400);
    }

    const targetType = "PROPOSAL";
    const targetObjid = String(proposalSrmObjid);
    const approvalTitle = opts.approvalTitle || `품의서 결재${head.request_mng_no ? " - " + head.request_mng_no : ""}`;
    const outProcessCode =
      process.env.AMARANTH_OUT_PROCESS_CODE_PROPOSAL ||
      process.env.AMARANTH_OUT_PROCESS_CODE || "";
    const formId = process.env.AMARANTH_FORM_ID_PROPOSAL || "1163";
    const compSeq = process.env.AMARANTH_COMP_SEQ || "1000";

    const existRes = await client.query(
      `SELECT objid, appro_key, status FROM amaranth_approval
        WHERE target_type=$1 AND target_objid=$2
        ORDER BY regdate DESC LIMIT 1`,
      [targetType, targetObjid],
    );

    let approKey: string;
    let mode: "insert" | "update_resubmit" | "update_reuse";
    let existingObjid: number | null = null;

    if (existRes.rowCount === 0) {
      approKey = "UB_" + Date.now().toString(36).toUpperCase();
      mode = "insert";
    } else {
      existingObjid = existRes.rows[0].objid;
      const existingStatus = String(existRes.rows[0].status || "");
      if (["reject", "delete", "create"].includes(existingStatus)) {
        approKey = "UB_" + Date.now().toString(36).toUpperCase();
        mode = "update_resubmit";
      } else {
        approKey = String(existRes.rows[0].appro_key || "");
        mode = "update_reuse";
      }
    }

    const ssoRes = await amaranth.getSsoUrl({
      empSeq,
      outProcessCode: outProcessCode || undefined,
      formId,
      approKey,
      subjectStr: opts.subjectStr || approvalTitle,
      mod: "W",
      compSeq,
      deptSeq: "",
      loginId: u.user_id,
    });

    const fullUrl: string = ssoRes?.resultData?.fullUrl || ssoRes?.fullUrl || "";
    const resultCode = String(ssoRes?.resultCode ?? ssoRes?.resultData?.resultCode ?? "");
    if (!fullUrl || (resultCode !== "0" && resultCode !== "")) {
      const msg = ssoRes?.resultMsg || ssoRes?.resultData?.resultMsg || "SSO URL 생성 실패";
      throw new AppError(`결재 연동 오류: ${msg}`, 502);
    }

    if (mode === "insert") {
      const objid = Date.now();
      await client.query(
        `INSERT INTO amaranth_approval
           (objid, target_objid, target_type, appro_key, out_process_code, form_id,
            status, emp_seq, comp_seq, dept_seq, writer, sso_url, regdate)
         VALUES ($1, $2, $3, $4, $5, $6, 'create', $7, $8, '', $9, $10, NOW())`,
        [objid, targetObjid, targetType, approKey, outProcessCode || null, formId, empSeq, compSeq, userId, fullUrl],
      );
    } else {
      const resetStatus = mode === "update_resubmit" ? "create" : null;
      await client.query(
        `UPDATE amaranth_approval
            SET appro_key=$2, sso_url=$3, writer=$4,
                status=COALESCE($5, status),
                editdate=NOW()
          WHERE objid=$1`,
        [existingObjid, approKey, fullUrl, userId, resetStatus],
      );
    }

    await client.query("COMMIT");
    logger.info("품의서 결재상신", { proposalSrmObjid, approKey, mode });
    return { fullUrl, approKey, status: "create", proposalObjid: targetObjid };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}
