/**
 * 작업지시 컨트롤러 (work_instruction + work_instruction_detail)
 */
import { Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
import { numberingRuleService } from "../services/numberingRuleService";
import { copyChecklistToSplit } from "./popProductionController";

// 자동 마이그레이션: work_instruction_detail에 routing_version_id + 품목별 일정/설비/작업조/작업자 컬럼 추가
let _migrationDone = false;
async function ensureDetailRoutingColumn() {
  if (_migrationDone) return;
  try {
    const pool = getPool();
    await pool.query("ALTER TABLE work_instruction_detail ADD COLUMN IF NOT EXISTS routing_version_id VARCHAR(500)");
    // 품목별 일정/설비/작업조/작업자 컬럼 (옵션 A — 다중선택 지원)
    await pool.query("ALTER TABLE work_instruction_detail ADD COLUMN IF NOT EXISTS start_date VARCHAR(500)");
    await pool.query("ALTER TABLE work_instruction_detail ADD COLUMN IF NOT EXISTS end_date VARCHAR(500)");
    await pool.query("ALTER TABLE work_instruction_detail ADD COLUMN IF NOT EXISTS equipment_ids VARCHAR(1000)");
    await pool.query("ALTER TABLE work_instruction_detail ADD COLUMN IF NOT EXISTS work_teams VARCHAR(200)");
    await pool.query("ALTER TABLE work_instruction_detail ADD COLUMN IF NOT EXISTS workers VARCHAR(1000)");
    _migrationDone = true;
  } catch { /* 이미 존재하거나 권한 문제 시 무시 */ }
}

// ─── 작업지시 목록 조회 (detail 기준 행 반환) ───
export async function getList(req: AuthenticatedRequest, res: Response) {
  try {
    await ensureDetailRoutingColumn();
    const companyCode = req.user!.companyCode;
    const { dateFrom, dateTo, status, progressStatus, keyword, page, pageSize } = req.query;

    // 페이지네이션 파라미터 파싱 (page 없으면 전체 반환 — 하위호환)
    const pageNum = page ? Math.max(1, parseInt(page as string, 10) || 1) : null;
    const sizeNum = pageSize ? Math.max(1, Math.min(1000, parseInt(pageSize as string, 10) || 20)) : null;
    const paginated = pageNum !== null && sizeNum !== null;

    const conditions: string[] = [];
    const params: any[] = [];
    let idx = 1;

    if (companyCode !== "*") {
      conditions.push(`wi.company_code = $${idx}`);
      params.push(companyCode);
      idx++;
    }
    if (dateFrom) {
      conditions.push(`wi.start_date >= $${idx}`);
      params.push(dateFrom);
      idx++;
    }
    if (dateTo) {
      conditions.push(`wi.end_date <= $${idx}`);
      params.push(dateTo);
      idx++;
    }
    if (status && status !== "all") {
      conditions.push(`wi.status = $${idx}`);
      params.push(status);
      idx++;
    }
    if (progressStatus && progressStatus !== "all") {
      conditions.push(`wi.progress_status = $${idx}`);
      params.push(progressStatus);
      idx++;
    }
    // keyword 검색: wi 자체 필드 + detail.item_number 존재 여부로 EXISTS
    if (keyword) {
      conditions.push(`(
        wi.work_instruction_no ILIKE $${idx}
        OR wi.worker ILIKE $${idx}
        OR EXISTS (
          SELECT 1 FROM work_instruction_detail dd
          LEFT JOIN item_info ii ON ii.item_number = dd.item_number AND ii.company_code = wi.company_code
          WHERE dd.work_instruction_id = wi.id
            AND (dd.item_number ILIKE $${idx} OR COALESCE(ii.item_name,'') ILIKE $${idx})
        )
      )`);
      params.push(`%${keyword}%`);
      idx++;
    }

    const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";

    const pool = getPool();

    // 페이지네이션 모드: WI 단위로 페이지 잘라낸 뒤 detail과 JOIN
    if (paginated) {
      // 1) 총 WI 개수 카운트
      const countSql = `
        SELECT COUNT(*)::int AS cnt
        FROM work_instruction wi
        ${whereClause}
      `;
      const countRes = await pool.query(countSql, params);
      const totalCount = countRes.rows[0]?.cnt ?? 0;

      // 2) 현재 페이지 WI id 목록 (최신 생성순, 동일 created_date일 때 번호 내림차순)
      const offset = (pageNum! - 1) * sizeNum!;
      const pageSql = `
        SELECT wi.id
        FROM work_instruction wi
        ${whereClause}
        ORDER BY wi.created_date DESC NULLS LAST, wi.work_instruction_no DESC
        LIMIT ${sizeNum} OFFSET ${offset}
      `;
      const pageRes = await pool.query(pageSql, params);
      const wiIds = pageRes.rows.map((r) => r.id);

      if (wiIds.length === 0) {
        return res.json({ success: true, data: [], totalCount, page: pageNum, pageSize: sizeNum });
      }

      // 3) 해당 WI들의 detail + 품목/설비/라우팅 JOIN
      const dataSql = `
        SELECT
          wi.id AS wi_id,
          wi.work_instruction_no,
          wi.status,
          wi.progress_status,
          wi.qty AS total_qty,
          wi.completed_qty,
          wi.start_date,
          wi.end_date,
          wi.equipment_id,
          wi.work_team,
          wi.worker,
          wi.remark AS wi_remark,
          wi.created_date,
          wi.batch_no,
          wi.cutting_plan_id,
          d.id AS detail_id,
          d.item_number,
          d.qty AS detail_qty,
          d.remark AS detail_remark,
          d.part_code,
          d.source_table,
          d.source_id,
          d.routing_version_id AS detail_routing_version_id,
          d.start_date AS detail_start_date,
          d.end_date AS detail_end_date,
          d.equipment_ids AS detail_equipment_ids,
          d.work_teams AS detail_work_teams,
          d.workers AS detail_workers,
          COALESCE(itm.item_name, '') AS item_name,
          COALESCE(itm.type, '') AS item_type,
          COALESCE(itm.size, '') AS item_spec,
          COALESCE(e.equipment_name, '') AS equipment_name,
          COALESCE(e.equipment_code, '') AS equipment_code,
          wi.routing AS routing_version_id,
          COALESCE(rv.version_name, '') AS routing_name,
          ROW_NUMBER() OVER (PARTITION BY wi.work_instruction_no ORDER BY d.created_date, d.id) AS detail_seq,
          COUNT(*) OVER (PARTITION BY wi.work_instruction_no) AS detail_count
        FROM work_instruction wi
        INNER JOIN work_instruction_detail d
          ON d.work_instruction_id = wi.id
        LEFT JOIN item_info itm
          ON itm.item_number = d.item_number AND itm.company_code = wi.company_code
        LEFT JOIN equipment_mng e
          ON wi.equipment_id = e.id AND wi.company_code = e.company_code
        LEFT JOIN item_routing_version rv
          ON wi.routing = rv.id AND rv.company_code = wi.company_code
        WHERE wi.id = ANY($1::varchar[])
        ORDER BY wi.created_date DESC NULLS LAST, wi.work_instruction_no DESC, d.created_date ASC, d.id ASC
      `;
      const dataRes = await pool.query(dataSql, [wiIds]);

      return res.json({
        success: true,
        data: dataRes.rows,
        totalCount,
        page: pageNum,
        pageSize: sizeNum,
      });
    }

    // 비페이지 모드 (하위호환): 기존 방식 유지, LATERAL만 LEFT JOIN으로 교체
    const query = `
      SELECT
        wi.id AS wi_id,
        wi.work_instruction_no,
        wi.status,
        wi.progress_status,
        wi.qty AS total_qty,
        wi.completed_qty,
        wi.start_date,
        wi.end_date,
        wi.equipment_id,
        wi.work_team,
        wi.worker,
        wi.remark AS wi_remark,
        wi.created_date,
        wi.batch_no,
        wi.cutting_plan_id,
        d.id AS detail_id,
        d.item_number,
        d.qty AS detail_qty,
        d.remark AS detail_remark,
        d.part_code,
        d.source_table,
        d.source_id,
        d.routing_version_id AS detail_routing_version_id,
        d.start_date AS detail_start_date,
        d.end_date AS detail_end_date,
        d.equipment_ids AS detail_equipment_ids,
        d.work_teams AS detail_work_teams,
        d.workers AS detail_workers,
        COALESCE(itm.item_name, '') AS item_name,
        COALESCE(itm.type, '') AS item_type,
        COALESCE(itm.size, '') AS item_spec,
        COALESCE(e.equipment_name, '') AS equipment_name,
        COALESCE(e.equipment_code, '') AS equipment_code,
        wi.routing AS routing_version_id,
        COALESCE(rv.version_name, '') AS routing_name,
        ROW_NUMBER() OVER (PARTITION BY wi.work_instruction_no ORDER BY d.created_date, d.id) AS detail_seq,
        COUNT(*) OVER (PARTITION BY wi.work_instruction_no) AS detail_count
      FROM work_instruction wi
      INNER JOIN work_instruction_detail d
        ON d.work_instruction_id = wi.id
      LEFT JOIN item_info itm
        ON itm.item_number = d.item_number AND itm.company_code = wi.company_code
      LEFT JOIN equipment_mng e ON wi.equipment_id = e.id AND wi.company_code = e.company_code
      LEFT JOIN item_routing_version rv ON wi.routing = rv.id AND rv.company_code = wi.company_code
      ${whereClause}
      ORDER BY wi.created_date DESC NULLS LAST, wi.work_instruction_no DESC, d.created_date ASC, d.id ASC
    `;

    const result = await pool.query(query, params);
    return res.json({ success: true, data: result.rows });
  } catch (error: any) {
    logger.error("작업지시 목록 조회 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 다음 작업지시번호 미리보기 ───
export async function previewNextNo(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    let wiNo: string;
    try {
      const rule = await numberingRuleService.getNumberingRuleByColumn(companyCode, "work_instruction", "work_instruction_no");
      if (rule) {
        wiNo = await numberingRuleService.previewCode(rule.ruleId, companyCode, {});
      } else { throw new Error("채번 규칙 없음"); }
    } catch {
      const pool = getPool();
      const today = new Date().toISOString().split("T")[0].replace(/-/g, "");
      const seqRes = await pool.query(
        `SELECT COUNT(*) + 1 AS seq FROM work_instruction WHERE company_code = $1 AND work_instruction_no LIKE $2`,
        [companyCode, `WI-${today}-%`]
      );
      wiNo = `WI-${today}-${String(seqRes.rows[0].seq).padStart(3, "0")}`;
    }
    return res.json({ success: true, instructionNo: wiNo });
  } catch (error: any) {
    logger.error("작업지시번호 미리보기 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 작업지시 저장 (신규/수정) ───
export async function save(req: AuthenticatedRequest, res: Response) {
  try {
    await ensureDetailRoutingColumn();
    const companyCode = req.user!.companyCode;
    const userId = req.user!.userId;
    const { id: editId, status: wiStatus, progressStatus, reason, startDate, endDate, equipmentId, workTeam, worker, remark, items, routing: routingVersionId, batchNo, cuttingPlanId } = req.body;

    if (!items || items.length === 0) {
      return res.status(400).json({ success: false, message: "품목을 선택해주세요" });
    }

    const pool = getPool();
    const client = await pool.connect();
    try {
      await client.query("BEGIN");
      let wiId: string;
      let wiNo: string;

      if (editId) {
        const check = await client.query(`SELECT id, work_instruction_no FROM work_instruction WHERE id = $1 AND company_code = $2`, [editId, companyCode]);
        if (check.rowCount === 0) throw new Error("작업지시를 찾을 수 없습니다");
        wiId = editId;
        wiNo = check.rows[0].work_instruction_no;
        await client.query(
          `UPDATE work_instruction SET status=$1, progress_status=$2, reason=$3, start_date=$4, end_date=$5, equipment_id=$6, work_team=$7, worker=$8, remark=$9, routing=$10, batch_no=COALESCE($11, batch_no), cutting_plan_id=COALESCE($12, cutting_plan_id), updated_date=NOW(), writer=$13 WHERE id=$14 AND company_code=$15`,
          [wiStatus||"일반", progressStatus||"", reason||"", startDate||"", endDate||"", equipmentId||"", workTeam||"", worker||"", remark||"", routingVersionId||null, batchNo||null, cuttingPlanId||null, userId, editId, companyCode]
        );
        await client.query(`DELETE FROM work_instruction_detail WHERE work_instruction_id=$1`, [wiId]);
      } else {
        try {
          const rule = await numberingRuleService.getNumberingRuleByColumn(companyCode, "work_instruction", "work_instruction_no");
          if (rule) { wiNo = await numberingRuleService.allocateCode(rule.ruleId, companyCode, {}); }
          else { throw new Error("채번 규칙 없음 - 폴백"); }
        } catch {
          const today = new Date().toISOString().split("T")[0].replace(/-/g, "");
          const seqRes = await client.query(`SELECT COUNT(*)+1 AS seq FROM work_instruction WHERE company_code=$1 AND work_instruction_no LIKE $2`, [companyCode, `WI-${today}-%`]);
          wiNo = `WI-${today}-${String(seqRes.rows[0].seq).padStart(3, "0")}`;
        }
        const insertRes = await client.query(
          `INSERT INTO work_instruction (id,company_code,work_instruction_no,status,progress_status,reason,start_date,end_date,equipment_id,work_team,worker,remark,routing,batch_no,cutting_plan_id,created_date,writer) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,NOW(),$15) RETURNING id`,
          [companyCode, wiNo, wiStatus||"일반", progressStatus||"", reason||"", startDate||"", endDate||"", equipmentId||"", workTeam||"", worker||"", remark||"", routingVersionId||null, batchNo||null, cuttingPlanId||null, userId]
        );
        wiId = insertRes.rows[0].id;
      }

      let totalQty = 0;
      let firstRouting: string | null = null;
      for (const item of items) {
        const itemRouting = item.routing || null;
        if (!firstRouting && itemRouting) firstRouting = itemRouting;
        totalQty += Number(item.qty || 0);
        await client.query(
          `INSERT INTO work_instruction_detail (id,company_code,work_instruction_no,work_instruction_id,item_number,qty,remark,source_table,source_id,part_code,routing_version_id,start_date,end_date,equipment_ids,work_teams,workers,created_date,writer) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,NOW(),$16)`,
          [
            companyCode,
            wiNo,
            wiId,
            item.itemNumber||item.itemCode||"",
            item.qty||"0",
            item.remark||"",
            item.sourceTable||"",
            item.sourceId||"",
            item.partCode||item.itemNumber||item.itemCode||"",
            itemRouting,
            item.startDate||"",
            item.endDate||"",
            item.equipmentIds||"",
            item.workTeams||"",
            item.workers||"",
            userId,
          ]
        );
      }

      // 마스터 qty/routing 자동 동기화 (디테일 합계 + 첫 번째 라우팅)
      const effectiveRouting = routingVersionId || firstRouting;
      await client.query(
        `UPDATE work_instruction SET qty = $1, routing = COALESCE(routing, $2) WHERE id = $3`,
        [String(totalQty), effectiveRouting, wiId]
      );

      await client.query("COMMIT");
      return res.json({ success: true, data: { id: wiId, workInstructionNo: wiNo } });
    } catch (txErr) { await client.query("ROLLBACK"); throw txErr; }
    finally { client.release(); }
  } catch (error: any) {
    logger.error("작업지시 저장 실패", { error: error.message, stack: error.stack });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 작업지시 삭제 ───
export async function remove(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { ids } = req.body;
    if (!ids || ids.length === 0) return res.status(400).json({ success: false, message: "삭제할 항목을 선택해주세요" });

    const pool = getPool();
    const client = await pool.connect();
    try {
      await client.query("BEGIN");
      // 디테일 삭제 (id 기반)
      await client.query(`DELETE FROM work_instruction_detail WHERE work_instruction_id=ANY($1)`, [ids]);
      const result = await client.query(`DELETE FROM work_instruction WHERE id=ANY($1) AND company_code=$2`, [ids, companyCode]);
      await client.query("COMMIT");
      return res.json({ success: true, deletedCount: result.rowCount });
    } catch (txErr) { await client.query("ROLLBACK"); throw txErr; }
    finally { client.release(); }
  } catch (error: any) {
    logger.error("작업지시 삭제 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 품목 소스 (페이징) ───
export async function getItemSource(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { keyword, page: ps, pageSize: pss } = req.query;
    const page = Math.max(1, parseInt(ps as string) || 1);
    const pageSize = Math.min(100, Math.max(1, parseInt(pss as string) || 20));
    const offset = (page - 1) * pageSize;

    const conds = ["company_code = $1"]; const params: any[] = [companyCode]; let idx = 2;
    if (keyword) { conds.push(`(item_number ILIKE $${idx} OR item_name ILIKE $${idx})`); params.push(`%${keyword}%`); idx++; }
    const w = conds.join(" AND ");
    const pool = getPool();
    const cnt = await pool.query(`SELECT COUNT(*) AS total FROM item_info WHERE ${w}`, params);
    params.push(pageSize, offset);
    const rows = await pool.query(`SELECT id, item_number AS item_code, item_name, COALESCE(size,'') AS spec FROM item_info WHERE ${w} ORDER BY item_name LIMIT $${idx} OFFSET $${idx+1}`, params);
    return res.json({ success: true, data: rows.rows, totalCount: parseInt(cnt.rows[0].total), page, pageSize });
  } catch (error: any) { return res.status(500).json({ success: false, message: error.message }); }
}

// ─── 수주 소스 (페이징) ───
export async function getSalesOrderSource(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { keyword, page: ps, pageSize: pss } = req.query;
    const page = Math.max(1, parseInt(ps as string) || 1);
    const pageSize = Math.min(100, Math.max(1, parseInt(pss as string) || 20));
    const offset = (page - 1) * pageSize;

    const conds = ["d.company_code = $1"]; const params: any[] = [companyCode]; let idx = 2;
    if (keyword) { conds.push(`(d.part_code ILIKE $${idx} OR COALESCE(i.item_name, d.part_name, d.part_code) ILIKE $${idx} OR d.order_no ILIKE $${idx})`); params.push(`%${keyword}%`); idx++; }
    const fromClause = `FROM sales_order_detail d LEFT JOIN LATERAL (SELECT item_name FROM item_info WHERE item_number = d.part_code AND company_code = d.company_code LIMIT 1) i ON true WHERE ${conds.join(" AND ")}`;
    const pool = getPool();
    const cnt = await pool.query(`SELECT COUNT(*) AS total ${fromClause}`, params);
    params.push(pageSize, offset);
    const rows = await pool.query(`SELECT d.id, d.order_no, d.part_code AS item_code, COALESCE(i.item_name, d.part_name, d.part_code) AS item_name, COALESCE(d.spec,'') AS spec, COALESCE(NULLIF(d.qty,'')::numeric,0) AS qty, d.due_date ${fromClause} ORDER BY d.created_date DESC LIMIT $${idx} OFFSET $${idx+1}`, params);
    return res.json({ success: true, data: rows.rows, totalCount: parseInt(cnt.rows[0].total), page, pageSize });
  } catch (error: any) { return res.status(500).json({ success: false, message: error.message }); }
}

// ─── 생산계획 소스 (페이징) ───
export async function getProductionPlanSource(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { keyword, page: ps, pageSize: pss } = req.query;
    const page = Math.max(1, parseInt(ps as string) || 1);
    const pageSize = Math.min(100, Math.max(1, parseInt(pss as string) || 20));
    const offset = (page - 1) * pageSize;

    const conds = ["p.company_code = $1"]; const params: any[] = [companyCode]; let idx = 2;
    if (keyword) { conds.push(`(p.plan_no ILIKE $${idx} OR p.item_code ILIKE $${idx} OR COALESCE(p.item_name,'') ILIKE $${idx})`); params.push(`%${keyword}%`); idx++; }
    const w = conds.join(" AND ");
    const pool = getPool();
    const cnt = await pool.query(`SELECT COUNT(*) AS total FROM production_plan_mng p WHERE ${w}`, params);
    params.push(pageSize, offset);
    // work_instruction_detail에서 해당 계획에 이미 내린 작업지시 수량 합계 → applied_qty, remain_qty
    const rows = await pool.query(
      `SELECT p.id, p.plan_no, p.item_code,
              COALESCE(p.item_name,'') AS item_name,
              COALESCE(p.plan_qty,0) AS plan_qty,
              p.start_date, p.end_date, p.status,
              COALESCE(p.equipment_name,'') AS equipment_name,
              COALESCE(wi.applied_qty, 0) AS applied_qty,
              (COALESCE(CAST(NULLIF(p.plan_qty::text, '') AS numeric), 0)
               - COALESCE(wi.applied_qty, 0)) AS remain_qty
         FROM production_plan_mng p
         LEFT JOIN (
           SELECT source_id,
                  SUM(COALESCE(CAST(NULLIF(qty, '') AS numeric), 0)) AS applied_qty
             FROM work_instruction_detail
            WHERE source_table = 'production_plan_mng'
              AND company_code = $1
            GROUP BY source_id
         ) wi ON wi.source_id = p.id::text
        WHERE ${w}
        ORDER BY p.created_date DESC
        LIMIT $${idx} OFFSET $${idx+1}`,
      params,
    );
    return res.json({ success: true, data: rows.rows, totalCount: parseInt(cnt.rows[0].total), page, pageSize });
  } catch (error: any) { return res.status(500).json({ success: false, message: error.message }); }
}

// ─── 사원 목록 (작업자 Select용) ───
export async function getEmployeeList(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const pool = getPool();
    let query: string;
    let params: any[];
    if (companyCode !== "*") {
      query = `SELECT user_id, user_name, dept_name FROM user_info WHERE company_code = $1 AND company_code != '*' ORDER BY user_name`;
      params = [companyCode];
    } else {
      query = `SELECT user_id, user_name, dept_name, company_code FROM user_info WHERE company_code != '*' ORDER BY user_name`;
      params = [];
    }
    const result = await pool.query(query, params);
    return res.json({ success: true, data: result.rows });
  } catch (error: any) {
    logger.error("사원 목록 조회 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 설비 목록 (Select용) ───
export async function getEquipmentList(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const pool = getPool();
    const cond = companyCode !== "*" ? "WHERE company_code = $1" : "";
    const params = companyCode !== "*" ? [companyCode] : [];
    const result = await pool.query(`SELECT id, equipment_code, equipment_name FROM equipment_mng ${cond} ORDER BY equipment_name`, params);
    return res.json({ success: true, data: result.rows });
  } catch (error: any) { return res.status(500).json({ success: false, message: error.message }); }
}

// ─── 품목의 라우팅 버전 + 공정 조회 ───
export async function getRoutingVersions(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { itemCode } = req.params;
    const pool = getPool();

    const versionsResult = await pool.query(
      `SELECT id, version_name, description, created_date, COALESCE(is_default, false) AS is_default
       FROM item_routing_version
       WHERE item_code = $1 AND company_code = $2
       ORDER BY is_default DESC, created_date DESC`,
      [itemCode, companyCode]
    );

    const routings = [];
    for (const version of versionsResult.rows) {
      const detailsResult = await pool.query(
        `SELECT rd.id AS routing_detail_id, rd.seq_no, rd.process_code,
                rd.is_required, rd.work_type,
                COALESCE(p.process_name, rd.process_code) AS process_name
         FROM item_routing_detail rd
         LEFT JOIN process_mng p ON p.process_code = rd.process_code AND p.company_code = rd.company_code
         WHERE rd.routing_version_id = $1 AND rd.company_code = $2
         ORDER BY rd.seq_no::integer`,
        [version.id, companyCode]
      );
      routings.push({ ...version, processes: detailsResult.rows });
    }

    return res.json({ success: true, data: routings });
  } catch (error: any) {
    logger.error("라우팅 버전 조회 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 품목별 라우팅 벌크 조회 (엑셀 업로드용) ───
export async function getRoutingVersionsBulk(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { itemCodes } = req.body as { itemCodes: string[] };

    if (!itemCodes || !Array.isArray(itemCodes) || itemCodes.length === 0) {
      return res.json({ success: true, data: {} });
    }

    const pool = getPool();
    const result: Record<string, { code: string; name: string }[]> = {};

    // 청크 단위로 분할 (PostgreSQL placeholder 제한 대응)
    const CHUNK_SIZE = 5000;
    for (let ci = 0; ci < itemCodes.length; ci += CHUNK_SIZE) {
      const chunk = itemCodes.slice(ci, ci + CHUNK_SIZE);

      // 1. 기본 라우팅 버전 조회
      const placeholders = chunk.map((_, i) => `$${i + 2}`).join(",");
      const versionsResult = await pool.query(
        `SELECT DISTINCT ON (item_code) id, item_code, version_name
         FROM item_routing_version
         WHERE company_code = $1 AND item_code IN (${placeholders})
         ORDER BY item_code, is_default DESC, created_date DESC`,
        [companyCode, ...chunk]
      );

      if (versionsResult.rows.length === 0) continue;

      // 2. 라우팅 디테일 조회
      const versionIds = versionsResult.rows.map((v: any) => v.id);
      const vPlaceholders = versionIds.map((_: any, i: number) => `$${i + 2}`).join(",");
      const detailsResult = await pool.query(
        `SELECT rd.routing_version_id, rd.process_code,
                COALESCE(p.process_name, rd.process_code) AS process_name
         FROM item_routing_detail rd
         LEFT JOIN process_mng p ON p.process_code = rd.process_code AND p.company_code = rd.company_code
         WHERE rd.company_code = $1 AND rd.routing_version_id IN (${vPlaceholders})
         ORDER BY rd.seq_no::integer`,
        [companyCode, ...versionIds]
      );

      // 3. 매핑
      const versionToItem: Record<string, string> = {};
      for (const v of versionsResult.rows) {
        versionToItem[v.id] = v.item_code;
      }
      for (const d of detailsResult.rows) {
        const itemCode = versionToItem[d.routing_version_id];
        if (!itemCode) continue;
        if (!result[itemCode]) result[itemCode] = [];
        result[itemCode].push({ code: d.process_code, name: d.process_name });
      }
    }

    return res.json({ success: true, data: result });
  } catch (error: any) {
    logger.error("벌크 라우팅 조회 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 작업지시 라우팅 변경 ───
export async function updateRouting(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { wiNo } = req.params;
    const { routingVersionId } = req.body;
    const pool = getPool();

    await pool.query(
      `UPDATE work_instruction SET routing = $1, updated_date = NOW() WHERE work_instruction_no = $2 AND company_code = $3`,
      [routingVersionId || null, wiNo, companyCode]
    );

    return res.json({ success: true });
  } catch (error: any) {
    logger.error("라우팅 변경 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 작업지시 전용 공정작업기준 조회 ───
export async function getWorkStandard(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const { wiNo } = req.params;
    const { routingVersionId } = req.query;
    const pool = getPool();

    if (!routingVersionId) {
      return res.status(400).json({ success: false, message: "routingVersionId 필요" });
    }

    // 라우팅 디테일(공정) 목록 조회
    const processesResult = await pool.query(
      `SELECT rd.id AS routing_detail_id, rd.seq_no, rd.process_code,
              COALESCE(p.process_name, rd.process_code) AS process_name
       FROM item_routing_detail rd
       LEFT JOIN process_mng p ON p.process_code = rd.process_code AND p.company_code = rd.company_code
       WHERE rd.routing_version_id = $1 AND rd.company_code = $2
       ORDER BY rd.seq_no::integer`,
      [routingVersionId, companyCode]
    );

    // 커스텀 작업기준이 있는지 확인
    const customCheck = await pool.query(
      `SELECT COUNT(*) AS cnt FROM wi_process_work_item WHERE work_instruction_no = $1 AND company_code = $2`,
      [wiNo, companyCode]
    );
    const hasCustom = parseInt(customCheck.rows[0].cnt) > 0;

    const processes = [];
    for (const proc of processesResult.rows) {
      let workItems;

      if (hasCustom) {
        // 커스텀 버전에서 조회
        const wiResult = await pool.query(
          `SELECT wi.id, wi.routing_detail_id, wi.work_phase, wi.title, wi.is_required, wi.sort_order, wi.description,
                  (SELECT COUNT(*) FROM wi_process_work_item_detail d WHERE d.wi_work_item_id = wi.id AND d.company_code = wi.company_code)::integer AS detail_count
           FROM wi_process_work_item wi
           WHERE wi.work_instruction_no = $1 AND wi.routing_detail_id = $2 AND wi.company_code = $3
           ORDER BY wi.work_phase, wi.sort_order`,
          [wiNo, proc.routing_detail_id, companyCode]
        );
        workItems = wiResult.rows;

        // 각 work_item의 상세도 로드
        for (const wi of workItems) {
          const detailsResult = await pool.query(
            `SELECT id, wi_work_item_id AS work_item_id, detail_type, content, is_required, sort_order, remark,
                    inspection_code, inspection_method, unit, lower_limit, upper_limit,
                    duration_minutes, input_type, lookup_target, display_fields,
                    process_inspection_apply, equip_inspection_apply,
                    condition_unit, condition_base_value, condition_tolerance,
                    condition_auto_collect, condition_plc_data
             FROM wi_process_work_item_detail
             WHERE wi_work_item_id = $1 AND company_code = $2
             ORDER BY sort_order`,
            [wi.id, companyCode]
          );
          wi.details = detailsResult.rows;
        }
      } else {
        // 원본에서 조회
        const origResult = await pool.query(
          `SELECT wi.id, wi.routing_detail_id, wi.work_phase, wi.title, wi.is_required, wi.sort_order, wi.description,
                  (SELECT COUNT(*) FROM process_work_item_detail d WHERE d.work_item_id = wi.id AND d.company_code = wi.company_code)::integer AS detail_count
           FROM process_work_item wi
           WHERE wi.routing_detail_id = $1 AND wi.company_code = $2
           ORDER BY wi.work_phase, wi.sort_order`,
          [proc.routing_detail_id, companyCode]
        );
        workItems = origResult.rows;

        for (const wi of workItems) {
          const detailsResult = await pool.query(
            `SELECT id, work_item_id, detail_type, content, is_required, sort_order, remark,
                    inspection_code, inspection_method, unit, lower_limit, upper_limit,
                    duration_minutes, input_type, lookup_target, display_fields,
                    process_inspection_apply, equip_inspection_apply,
                    condition_unit, condition_base_value, condition_tolerance,
                    condition_auto_collect, condition_plc_data
             FROM process_work_item_detail
             WHERE work_item_id = $1 AND company_code = $2
             ORDER BY sort_order`,
            [wi.id, companyCode]
          );
          wi.details = detailsResult.rows;
        }
      }

      processes.push({
        ...proc,
        workItems,
      });
    }

    return res.json({ success: true, data: { processes, isCustom: hasCustom } });
  } catch (error: any) {
    logger.error("작업지시 공정작업기준 조회 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

/**
 * wi_* 편집 시 마스터 체크리스트 스냅샷을 재투영한다.
 * 접수(work_order_process_result) 가 0건일 때만 동기화되며, 1건 이상이면 스냅샷 불변.
 * 트랜잭션 내에서 호출되어야 한다 (caller 가 BEGIN/COMMIT 관리).
 *
 * @param routingDetailId null 이면 해당 작업지시의 모든 routing detail 동기화
 * @returns synced: 실제 동기화 수행 여부, affectedProcesses: 재복사된 마스터 공정 수
 */
async function syncMasterChecklistFromWi(
  client: { query: (text: string, values?: any[]) => Promise<any> },
  workInstructionNo: string,
  routingDetailId: string | null,
  companyCode: string,
  userId: string,
): Promise<{ synced: boolean; affectedProcesses: number; reason?: string }> {
  // 1. 작업지시 id 조회
  const wiRow = await client.query(
    `SELECT id FROM work_instruction WHERE work_instruction_no = $1 AND company_code = $2`,
    [workInstructionNo, companyCode],
  );
  if (wiRow.rowCount === 0) {
    return { synced: false, affectedProcesses: 0, reason: "work_instruction not found" };
  }
  const wiId = wiRow.rows[0].id as string;

  // 2. advisory lock — 편집/접수 동시성 보호
  await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [
    `wi_snapshot:${companyCode}:${wiId}`,
  ]);

  // 3. 접수 건수 확인
  const acceptCount = await client.query(
    `SELECT COUNT(*)::int AS cnt FROM work_order_process_result wopr
     JOIN work_order_process wop ON wop.id = wopr.work_order_process_id
     WHERE wop.wo_id = $1 AND wop.company_code = $2 AND wopr.company_code = $2`,
    [wiId, companyCode],
  );
  if ((acceptCount.rows[0]?.cnt ?? 0) > 0) {
    return { synced: false, affectedProcesses: 0, reason: "accepted_count > 0" };
  }

  // 4. 대상 마스터 공정 목록
  const masterQuery = routingDetailId
    ? `SELECT id, routing_detail_id FROM work_order_process
       WHERE wo_id = $1 AND routing_detail_id = $2 AND company_code = $3`
    : `SELECT id, routing_detail_id FROM work_order_process
       WHERE wo_id = $1 AND company_code = $2`;
  const masterParams = routingDetailId
    ? [wiId, routingDetailId, companyCode]
    : [wiId, companyCode];
  const masters = await client.query(masterQuery, masterParams);

  let affected = 0;
  for (const m of masters.rows) {
    // 5. 기존 마스터 스냅샷 삭제
    await client.query(
      `DELETE FROM process_work_result WHERE work_order_process_id = $1 AND company_code = $2`,
      [m.id, companyCode],
    );
    // 6. 재복사 — copyChecklistToSplit 재활용 (wi_* 우선, 없으면 원본 fallback)
    await copyChecklistToSplit(
      client,
      m.id,
      m.id,
      m.routing_detail_id,
      companyCode,
      userId,
      { workInstructionNo },
    );
    affected++;
  }
  return { synced: true, affectedProcesses: affected };
}

// ─── 원본 공정작업기준 -> 작업지시 전용 복사 ───
export async function copyWorkStandard(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const userId = req.user!.userId;
    const { wiNo } = req.params;
    const { routingVersionId } = req.body;
    const pool = getPool();
    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      // 기존 커스텀 데이터 삭제
      const existingItems = await client.query(
        `SELECT id FROM wi_process_work_item WHERE work_instruction_no = $1 AND company_code = $2`,
        [wiNo, companyCode]
      );
      for (const row of existingItems.rows) {
        await client.query(
          `DELETE FROM wi_process_work_item_detail WHERE wi_work_item_id = $1 AND company_code = $2`,
          [row.id, companyCode]
        );
      }
      await client.query(
        `DELETE FROM wi_process_work_item WHERE work_instruction_no = $1 AND company_code = $2`,
        [wiNo, companyCode]
      );

      // 라우팅 디테일 목록 조회
      const routingDetails = await client.query(
        `SELECT id FROM item_routing_detail WHERE routing_version_id = $1 AND company_code = $2`,
        [routingVersionId, companyCode]
      );

      // 각 공정(routing_detail)별 원본 작업항목 복사
      for (const rd of routingDetails.rows) {
        const origItems = await client.query(
          `SELECT * FROM process_work_item WHERE routing_detail_id = $1 AND company_code = $2`,
          [rd.id, companyCode]
        );

        for (const origItem of origItems.rows) {
          const newItemResult = await client.query(
            `INSERT INTO wi_process_work_item (id, company_code, work_instruction_no, routing_detail_id, work_phase, title, is_required, sort_order, description, source_work_item_id, writer)
             VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id`,
            [companyCode, wiNo, rd.id, origItem.work_phase, origItem.title, origItem.is_required, origItem.sort_order, origItem.description, origItem.id, userId]
          );
          const newItemId = newItemResult.rows[0].id;

          // 상세 복사
          const origDetails = await client.query(
            `SELECT * FROM process_work_item_detail WHERE work_item_id = $1 AND company_code = $2`,
            [origItem.id, companyCode]
          );

          for (const origDetail of origDetails.rows) {
            await client.query(
              `INSERT INTO wi_process_work_item_detail (id, company_code, wi_work_item_id, detail_type, content, is_required, sort_order, remark, inspection_code, inspection_method, unit, lower_limit, upper_limit, duration_minutes, input_type, lookup_target, display_fields, process_inspection_apply, equip_inspection_apply, condition_unit, condition_base_value, condition_tolerance, condition_auto_collect, condition_plc_data, writer)
               VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)`,
              [companyCode, newItemId, origDetail.detail_type, origDetail.content, origDetail.is_required, origDetail.sort_order, origDetail.remark, origDetail.inspection_code, origDetail.inspection_method, origDetail.unit, origDetail.lower_limit, origDetail.upper_limit, origDetail.duration_minutes, origDetail.input_type, origDetail.lookup_target, origDetail.display_fields, origDetail.process_inspection_apply || null, origDetail.equip_inspection_apply || null, origDetail.condition_unit || null, origDetail.condition_base_value || null, origDetail.condition_tolerance || null, origDetail.condition_auto_collect || null, origDetail.condition_plc_data || null, userId]
            );
          }
        }
      }

      const sync = await syncMasterChecklistFromWi(client, wiNo, null, companyCode, userId);
      logger.info("[work-instruction] wi_* copy 후 마스터 스냅샷 동기화", { wiNo, ...sync });
      await client.query("COMMIT");
      logger.info("공정작업기준 복사 완료", { companyCode, wiNo, routingVersionId });
      return res.json({ success: true });
    } catch (txErr) {
      await client.query("ROLLBACK");
      throw txErr;
    } finally {
      client.release();
    }
  } catch (error: any) {
    logger.error("공정작업기준 복사 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 작업지시 전용 공정작업기준 저장 (일괄) ───
export async function saveWorkStandard(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const userId = req.user!.userId;
    const { wiNo } = req.params;
    const { routingDetailId, workItems } = req.body;
    const pool = getPool();
    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      // 해당 공정의 기존 커스텀 데이터 삭제
      const existing = await client.query(
        `SELECT id FROM wi_process_work_item WHERE work_instruction_no = $1 AND routing_detail_id = $2 AND company_code = $3`,
        [wiNo, routingDetailId, companyCode]
      );
      for (const row of existing.rows) {
        await client.query(
          `DELETE FROM wi_process_work_item_detail WHERE wi_work_item_id = $1 AND company_code = $2`,
          [row.id, companyCode]
        );
      }
      await client.query(
        `DELETE FROM wi_process_work_item WHERE work_instruction_no = $1 AND routing_detail_id = $2 AND company_code = $3`,
        [wiNo, routingDetailId, companyCode]
      );

      // 새 데이터 삽입
      // NOTE: wi_process_work_item / wi_process_work_item_detail.id 컬럼에 DEFAULT(gen_random_uuid()) 누락
      //       → id를 명시하지 않으면 NULL 저장되어 재조회 시 wi_work_item_id 매칭 실패(0건 반환)로 이어짐.
      //       원본 테이블(process_work_item) DEFAULT와 동기되지 않은 스키마 이슈. 여기서 명시 바인딩으로 회피.
      for (const wi of workItems) {
        const wiResult = await client.query(
          `INSERT INTO wi_process_work_item (id, company_code, work_instruction_no, routing_detail_id, work_phase, title, is_required, sort_order, description, source_work_item_id, writer)
           VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id`,
          [companyCode, wiNo, routingDetailId, wi.work_phase, wi.title, wi.is_required, wi.sort_order, wi.description || null, wi.source_work_item_id || null, userId]
        );
        const newId = wiResult.rows[0].id;

        if (wi.details && Array.isArray(wi.details)) {
          for (const d of wi.details) {
            await client.query(
              `INSERT INTO wi_process_work_item_detail (id, company_code, wi_work_item_id, detail_type, content, is_required, sort_order, remark, inspection_code, inspection_method, unit, lower_limit, upper_limit, duration_minutes, input_type, lookup_target, display_fields, process_inspection_apply, equip_inspection_apply, condition_unit, condition_base_value, condition_tolerance, condition_auto_collect, condition_plc_data, writer)
               VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)`,
              [companyCode, newId, d.detail_type, d.content, d.is_required, d.sort_order, d.remark || null, d.inspection_code || null, d.inspection_method || null, d.unit || null, d.lower_limit || null, d.upper_limit || null, d.duration_minutes || null, d.input_type || null, d.lookup_target || null, d.display_fields || null, d.process_inspection_apply || null, d.equip_inspection_apply || null, d.condition_unit || null, d.condition_base_value || null, d.condition_tolerance || null, d.condition_auto_collect || null, d.condition_plc_data || null, userId]
            );
          }
        }
      }

      const sync = await syncMasterChecklistFromWi(client, wiNo, routingDetailId, companyCode, userId);
      logger.info("[work-instruction] wi_* save 후 마스터 스냅샷 동기화", { wiNo, routingDetailId, ...sync });
      await client.query("COMMIT");
      logger.info("작업지시 공정작업기준 저장 완료", { companyCode, wiNo, routingDetailId });
      return res.json({ success: true });
    } catch (txErr) {
      await client.query("ROLLBACK");
      throw txErr;
    } finally {
      client.release();
    }
  } catch (error: any) {
    logger.error("작업지시 공정작업기준 저장 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}

// ─── 작업지시 전용 커스텀 데이터 삭제 (원본으로 초기화) ───
export async function resetWorkStandard(req: AuthenticatedRequest, res: Response) {
  try {
    const companyCode = req.user!.companyCode;
    const userId = req.user!.userId;
    const { wiNo } = req.params;
    const pool = getPool();
    const client = await pool.connect();

    try {
      await client.query("BEGIN");
      const items = await client.query(
        `SELECT id FROM wi_process_work_item WHERE work_instruction_no = $1 AND company_code = $2`,
        [wiNo, companyCode]
      );
      for (const row of items.rows) {
        await client.query(
          `DELETE FROM wi_process_work_item_detail WHERE wi_work_item_id = $1 AND company_code = $2`,
          [row.id, companyCode]
        );
      }
      await client.query(
        `DELETE FROM wi_process_work_item WHERE work_instruction_no = $1 AND company_code = $2`,
        [wiNo, companyCode]
      );
      const sync = await syncMasterChecklistFromWi(client, wiNo, null, companyCode, userId);
      logger.info("[work-instruction] wi_* reset 후 마스터 스냅샷 원본 복원", { wiNo, ...sync });
      await client.query("COMMIT");
      logger.info("작업지시 공정작업기준 초기화", { companyCode, wiNo });
      return res.json({ success: true });
    } catch (txErr) {
      await client.query("ROLLBACK");
      throw txErr;
    } finally {
      client.release();
    }
  } catch (error: any) {
    logger.error("작업지시 공정작업기준 초기화 실패", { error: error.message });
    return res.status(500).json({ success: false, message: error.message });
  }
}
