/**
 * 절단계획 서비스
 * - 원자재 / 수주 / 품목 조회
 * - 절단계획 CRUD (헤더 + 품목 + 원판 + 피스 좌표)
 */

import { getPool } from "../database/db";

// ─────────────────────────────────────────────────────────
// 조회: 원자재 (item_info.division = 'CAT_DIV_RAW_MAT')
// ─────────────────────────────────────────────────────────
export async function getMaterials(companyCode: string, cutType: string) {
  const pool = getPool();
  const q = `
    SELECT
      ii.id, ii.item_number, ii.item_name,
      COALESCE(ii.width::numeric, 0)     AS width,
      COALESCE(ii.height::numeric, 0)    AS height,
      COALESCE(ii.thickness::numeric, 0) AS thickness,
      ii.size, ii.material,
      COALESCE(inv.stock, 0) AS stock,
      COALESCE(inv.safety, 0) AS safety_stock
    FROM item_info ii
    LEFT JOIN (
      SELECT item_code,
             SUM(COALESCE(NULLIF(current_qty,'')::numeric, 0)) AS stock,
             SUM(COALESCE(NULLIF(safety_qty,'')::numeric, 0))  AS safety
      FROM inventory_stock
      WHERE company_code = $1
      GROUP BY item_code
    ) inv ON inv.item_code = ii.item_number
    WHERE ii.company_code = $1
      AND ii.division = 'CAT_DIV_RAW_MAT'
      AND COALESCE(ii.status,'active') <> 'deleted'
    ORDER BY ii.item_name
  `;
  const r = await pool.query(q, [companyCode]);
  return r.rows.map((row: any) => ({
    ...row,
    stock: Number(row.stock) || 0,
    safety_stock: Number(row.safety_stock) || 0,
    length: cutType === "length" ? Number(row.width || 0) : 0,
    unit: cutType === "length" ? "개" : "장",
  }));
}

// ─────────────────────────────────────────────────────────
// 조회: 품목 검색 (완제품)
// ─────────────────────────────────────────────────────────
export async function searchItems(companyCode: string, keyword?: string) {
  const pool = getPool();
  const params: any[] = [companyCode];
  let where = `company_code = $1 AND division <> 'CAT_DIV_RAW_MAT'`;
  if (keyword) {
    params.push(`%${keyword}%`);
    where += ` AND (item_name ILIKE $2 OR item_number ILIKE $2)`;
  }
  const q = `
    SELECT id, item_number, item_name, size,
           COALESCE(width::numeric,0)  AS width,
           COALESCE(height::numeric,0) AS height,
           COALESCE(thickness::numeric,0) AS thickness
    FROM item_info
    WHERE ${where}
    ORDER BY item_name
    LIMIT 200
  `;
  const r = await pool.query(q, params);
  return r.rows;
}

// ─────────────────────────────────────────────────────────
// 조회: 수주 소스 — 페이지네이션 + 계획/출하로 넘어간 건 제외 옵션
// ─────────────────────────────────────────────────────────
export async function getOrders(
  companyCode: string,
  opts?: { from?: string; to?: string; page?: number; limit?: number; excludeInPlan?: boolean; keyword?: string }
) {
  const pool = getPool();
  const params: any[] = [companyCode];
  let where = `so.company_code = $1 AND COALESCE(so.part_name,'') <> ''`;
  if (opts?.from)    { params.push(opts.from);          where += ` AND so.order_date >= $${params.length}`; }
  if (opts?.to)      { params.push(opts.to);            where += ` AND so.order_date <= $${params.length}`; }
  if (opts?.keyword) {
    params.push(`%${opts.keyword}%`);
    where += ` AND (so.order_no ILIKE $${params.length} OR so.part_name ILIKE $${params.length} OR so.part_code ILIKE $${params.length})`;
  }

  // 생산계획/출하계획으로 넘어간 수주만 제외 (절단계획은 본 화면에서 관리하므로 보여줌 → 배치번호로 식별)
  if (opts?.excludeInPlan) {
    where += `
      AND NOT EXISTS (SELECT 1 FROM production_plan_mng pp WHERE pp.order_no = so.order_no AND pp.company_code = so.company_code)
      AND NOT EXISTS (SELECT 1 FROM shipment_plan      sp WHERE sp.sales_order_id = so.id AND sp.company_code = so.company_code)
    `;
  }

  // 총 건수
  const cntQ = `SELECT COUNT(*)::int AS total FROM sales_order_mng so WHERE ${where}`;
  const cntR = await pool.query(cntQ, params);
  const total = cntR.rows[0]?.total || 0;

  // 페이지네이션
  const page  = Math.max(1, opts?.page  || 1);
  const limit = Math.min(1000, Math.max(1, opts?.limit || 100));
  const offset = (page - 1) * limit;

  const q = `
    SELECT so.order_no, so.order_date, so.due_date, so.partner_id,
           so.part_code, so.part_name, so.spec, so.material,
           COALESCE(so.order_qty::numeric,0)   AS order_qty,
           COALESCE(so.balance_qty::numeric,0) AS balance_qty,
           so.status,
           ii.id   AS item_id,
           ii.item_name AS item_name,
           cpm.id      AS batch_id,
           cpm.plan_no AS batch_no
    FROM sales_order_mng so
    LEFT JOIN item_info ii
           ON ii.item_number = so.part_code
          AND ii.company_code = so.company_code
    LEFT JOIN cutting_plan_item cpi
           ON cpi.src_no = so.order_no
    LEFT JOIN cutting_plan_mng cpm
           ON cpm.id = cpi.plan_id
          AND cpm.company_code = so.company_code
    WHERE ${where}
    ORDER BY cpm.plan_no DESC NULLS LAST, so.order_date DESC NULLS LAST, so.order_no DESC
    LIMIT ${limit} OFFSET ${offset}
  `;
  const r = await pool.query(q, params);
  return { rows: r.rows, total, page, limit };
}

// ─────────────────────────────────────────────────────────
// 조회: 계획 목록
// ─────────────────────────────────────────────────────────
export async function getPlans(
  companyCode: string,
  filter: { from?: string; to?: string; planNo?: string; status?: string }
) {
  const pool = getPool();
  const params: any[] = [companyCode];
  let where = `company_code = $1`;
  if (filter.from)   { params.push(filter.from);   where += ` AND plan_date_from >= $${params.length}`; }
  if (filter.to)     { params.push(filter.to);     where += ` AND plan_date_to   <= $${params.length}`; }
  if (filter.planNo) { params.push(`%${filter.planNo}%`); where += ` AND plan_no ILIKE $${params.length}`; }
  if (filter.status) { params.push(filter.status); where += ` AND status = $${params.length}`; }
  const q = `
    SELECT id, plan_no, plan_date_from, plan_date_to, cut_type, pack_mode, calc_mode,
           status, partner_id, total_sheets, util_rate, created_date
    FROM cutting_plan_mng
    WHERE ${where}
    ORDER BY id DESC
    LIMIT 200
  `;
  const r = await pool.query(q, params);
  return r.rows;
}

// ─────────────────────────────────────────────────────────
// 조회: 계획 상세 (헤더 + 품목 + 원판 + 피스)
// ─────────────────────────────────────────────────────────
export async function getPlanDetail(companyCode: string, planId: number) {
  const pool = getPool();
  const hdr = await pool.query(
    `SELECT * FROM cutting_plan_mng WHERE id = $1 AND company_code = $2`,
    [planId, companyCode]
  );
  if (!hdr.rows.length) return null;

  // item_info JOIN으로 item_number/item_name 복원. item_id가 null인 옛 데이터는 src_no → sales_order_mng.part_code로 fallback.
  const items  = await pool.query(
    `SELECT cpi.*,
            COALESCE(ii_direct.item_number, ii_order.item_number) AS item_number,
            COALESCE(ii_direct.item_name,   ii_order.item_name, cpi.item_name) AS item_name_resolved
       FROM cutting_plan_item cpi
       LEFT JOIN item_info ii_direct ON ii_direct.id::text = cpi.item_id
       LEFT JOIN sales_order_mng so  ON so.order_no = cpi.src_no AND so.company_code = $2
       LEFT JOIN item_info ii_order  ON ii_order.item_number = so.part_code AND ii_order.company_code = $2
      WHERE cpi.plan_id = $1
      ORDER BY cpi.seq, cpi.id`,
    [planId, companyCode]
  );
  const sheets = await pool.query(`SELECT * FROM cutting_plan_sheet WHERE plan_id = $1 ORDER BY sheet_no`, [planId]);
  const sheetIds = sheets.rows.map((s: any) => s.id);
  let placements: any[] = [];
  if (sheetIds.length) {
    const p = await pool.query(
      `SELECT * FROM cutting_plan_placement WHERE sheet_id = ANY($1::int[]) ORDER BY sheet_id, placement_order, id`,
      [sheetIds]
    );
    placements = p.rows;
  }
  return { header: hdr.rows[0], items: items.rows, sheets: sheets.rows, placements };
}

// ─────────────────────────────────────────────────────────
// 다음 계획번호 생성: CP-YYYY-NNNN
// ─────────────────────────────────────────────────────────
export async function nextPlanNo(companyCode: string): Promise<string> {
  const pool = getPool();
  const y = new Date().getFullYear();
  const prefix = `CP-${y}-`;
  const r = await pool.query(
    `SELECT plan_no FROM cutting_plan_mng
     WHERE company_code=$1 AND plan_no LIKE $2
     ORDER BY plan_no DESC LIMIT 1`,
    [companyCode, `${prefix}%`]
  );
  let next = 1;
  if (r.rows.length) {
    const last = r.rows[0].plan_no as string;
    const tail = parseInt(last.substring(prefix.length), 10);
    if (!isNaN(tail)) next = tail + 1;
  }
  return `${prefix}${String(next).padStart(4, "0")}`;
}

// ─────────────────────────────────────────────────────────
// 계획 저장 (신규 또는 업데이트) — 헤더+자식 전체 대체
// payload: { header, items, sheets, placements }
//   sheets[i].placements[j] 구조도 허용 (중첩형)
// ─────────────────────────────────────────────────────────
export async function savePlan(companyCode: string, userId: string, payload: any) {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");

    const h = payload.header || {};
    let planId: number;

    if (h.id) {
      // UPDATE 헤더
      const up = await client.query(
        `UPDATE cutting_plan_mng SET
           plan_no=$1, plan_date_from=$2, plan_date_to=$3,
           cut_type=$4, calc_mode=$5, pack_mode=$6,
           mat_item_id=$7, mat_item_id_2=$8,
           kerf=$9, margin=$10, min_remnant=$11, min_reuse=$12,
           partner_id=$13, status=$14,
           total_sheets=$15, total_pieces=$16, util_rate=$17, total_loss=$18, rotated_count=$19,
           remarks=$20, updated_date=NOW(), updated_by=$21
         WHERE id=$22 AND company_code=$23
         RETURNING id`,
        [
          h.plan_no, h.plan_date_from || null, h.plan_date_to || null,
          h.cut_type || "area", h.calc_mode || "auto", h.pack_mode || "mixed",
          h.mat_item_id || null, h.mat_item_id_2 || null,
          h.kerf ?? 3, h.margin ?? 2, h.min_remnant ?? 100, h.min_reuse ?? 100,
          h.partner_id || null, h.status || "draft",
          h.total_sheets ?? null, h.total_pieces ?? null, h.util_rate ?? null, h.total_loss ?? null, h.rotated_count ?? null,
          h.remarks || null, userId,
          h.id, companyCode,
        ]
      );
      if (!up.rows.length) throw new Error("계획을 찾을 수 없거나 권한이 없습니다");
      planId = up.rows[0].id;

      // 자식 전체 삭제 후 재등록
      await client.query(`DELETE FROM cutting_plan_placement WHERE sheet_id IN (SELECT id FROM cutting_plan_sheet WHERE plan_id=$1)`, [planId]);
      await client.query(`DELETE FROM cutting_plan_sheet     WHERE plan_id=$1`, [planId]);
      await client.query(`DELETE FROM cutting_plan_item      WHERE plan_id=$1`, [planId]);
    } else {
      // INSERT 헤더
      const plan_no = h.plan_no || (await nextPlanNo(companyCode));
      const ins = await client.query(
        `INSERT INTO cutting_plan_mng
          (company_code, plan_no, plan_date_from, plan_date_to,
           cut_type, calc_mode, pack_mode,
           mat_item_id, mat_item_id_2,
           kerf, margin, min_remnant, min_reuse,
           partner_id, status,
           total_sheets, total_pieces, util_rate, total_loss, rotated_count,
           remarks, created_by)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)
         RETURNING id`,
        [
          companyCode, plan_no, h.plan_date_from || null, h.plan_date_to || null,
          h.cut_type || "area", h.calc_mode || "auto", h.pack_mode || "mixed",
          h.mat_item_id || null, h.mat_item_id_2 || null,
          h.kerf ?? 3, h.margin ?? 2, h.min_remnant ?? 100, h.min_reuse ?? 100,
          h.partner_id || null, h.status || "draft",
          h.total_sheets ?? null, h.total_pieces ?? null, h.util_rate ?? null, h.total_loss ?? null, h.rotated_count ?? null,
          h.remarks || null, userId,
        ]
      );
      planId = ins.rows[0].id;
    }

    // 품목 저장 — 원래 index (프론트 itemIdx) 를 id 매핑용으로 추적
    // 한 PlanItem이 여러 수주를 합산했을 때(src_orders.length > 1)는 수주별 row 분리 저장
    // (각 수주에 batch_no 연결되도록). 첫 row에만 piece 정보 + 나머지는 같은 메타 + qty 0.
    const itemIdMap = new Map<number, number>(); // 프론트 itemIdx → DB id (첫 row)
    const items = payload.items || [];
    for (let i = 0; i < items.length; i++) {
      const it = items[i];
      const srcList: (string | null)[] =
        Array.isArray(it.src_orders) && it.src_orders.length > 0
          ? it.src_orders
          : [it.src_no || null];
      let firstId: number | null = null;
      for (let k = 0; k < srcList.length; k++) {
        const orderNo = srcList[k];
        const r = await client.query(
          `INSERT INTO cutting_plan_item
            (plan_id, seq, src_type, src_no, item_id, item_name, item_spec,
             width, height, length, qty, dir, color, placed_qty)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
           RETURNING id`,
          [
            planId, it.seq ?? i + 1,
            it.src_type || (orderNo ? "order" : "manual"), orderNo,
            it.item_id || null, it.item_name || "", it.item_spec || null,
            it.width ?? null, it.height ?? null, it.length ?? null,
            k === 0 ? (it.qty ?? 0) : 0, // 첫 row에만 qty (배치/통계용)
            it.dir || "무관", it.color || null,
            k === 0 ? (it.placed_qty ?? 0) : 0,
          ]
        );
        if (firstId === null) firstId = r.rows[0].id;
      }
      if (firstId !== null) itemIdMap.set(i, firstId);
    }

    // 원판(시트) + 배치 저장 — payload.sheets[i].placements[] 중첩형 지원
    const sheets = payload.sheets || [];
    for (let s = 0; s < sheets.length; s++) {
      const sh = sheets[s];
      const sr = await client.query(
        `INSERT INTO cutting_plan_sheet
          (plan_id, sheet_no, mat_item_id, mat_name,
           mat_width, mat_height, mat_length,
           util_rate, used_area, remnant_area, used_length, remnant_length, group_key, remnants)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
         RETURNING id`,
        [
          planId, sh.sheet_no ?? s + 1, sh.mat_item_id || null, sh.mat_name || null,
          sh.mat_width ?? null, sh.mat_height ?? null, sh.mat_length ?? null,
          sh.util_rate ?? null, sh.used_area ?? null, sh.remnant_area ?? null,
          sh.used_length ?? null, sh.remnant_length ?? null, sh.group_key || null,
          Array.isArray(sh.remnants) ? JSON.stringify(sh.remnants) : null,
        ]
      );
      const sheetId = sr.rows[0].id;

      const placements = sh.placements || [];
      for (let p = 0; p < placements.length; p++) {
        const pl = placements[p];
        const planItemId = pl.plan_item_id ?? (pl.itemIdx != null ? itemIdMap.get(pl.itemIdx) : null);
        await client.query(
          `INSERT INTO cutting_plan_placement
            (sheet_id, plan_item_id, x, y, w, h, start_x, seg_length, rotated, placement_order)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`,
          [
            sheetId, planItemId || null,
            pl.x ?? null, pl.y ?? null, pl.w ?? null, pl.h ?? null,
            pl.start_x ?? null, pl.seg_length ?? null,
            !!pl.rotated, pl.placement_order ?? p,
          ]
        );
      }
    }

    await client.query("COMMIT");
    return { id: planId, plan_no: payload.header?.plan_no };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─────────────────────────────────────────────────────────
// 삭제
// ─────────────────────────────────────────────────────────
export async function deletePlan(companyCode: string, planId: number) {
  const pool = getPool();
  const r = await pool.query(
    `DELETE FROM cutting_plan_mng WHERE id=$1 AND company_code=$2 RETURNING id`,
    [planId, companyCode]
  );
  return r.rows.length > 0;
}
