import { Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import { query, queryOne, transaction } from "../database/db";
import { PoolClient } from "pg";
import { NodeFlowExecutionService } from "../services/nodeFlowExecutionService";

// 트랜잭션 내부에서 throw하고 외부에서 instanceof로 구분하기 위한 커스텀 에러
class ValidationError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = "ValidationError";
  }
}

// ============================================================
// 결재 정의 (Approval Definitions) CRUD
// ============================================================

export class ApprovalDefinitionController {
  // 결재 유형 목록 조회
  static async getDefinitions(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { is_active, search } = req.query;

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

      // SUPER_ADMIN은 전체 조회, 일반 회사는 자사 데이터만
      if (companyCode === "*") {
        // 전체 조회 (company_code 필터 없음)
      } else {
        conditions.push(`company_code = $${idx++}`);
        params.push(companyCode);
      }

      if (is_active) {
        conditions.push(`is_active = $${idx++}`);
        params.push(is_active);
      }

      if (search) {
        // ILIKE에서 같은 파라미터를 두 조건에서 참조 (파라미터는 1개만 push)
        conditions.push(`(definition_name ILIKE $${idx} OR definition_name_eng ILIKE $${idx})`);
        params.push(`%${search}%`);
        idx++;
      }

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

      const rows = await query<any>(
        `SELECT * FROM approval_definitions ${whereClause} ORDER BY company_code, definition_id ASC`,
        params
      );

      return res.json({ success: true, data: rows });
    } catch (error) {
      console.error("결재 유형 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 유형 목록 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 유형 상세 조회
  static async getDefinition(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      // SUPER_ADMIN은 company_code 필터 없이 조회 가능
      const row = await queryOne<any>(
        companyCode === "*"
          ? "SELECT * FROM approval_definitions WHERE definition_id = $1"
          : "SELECT * FROM approval_definitions WHERE definition_id = $1 AND company_code = $2",
        companyCode === "*" ? [id] : [id, companyCode]
      );

      if (!row) {
        return res.status(404).json({ success: false, message: "결재 유형을 찾을 수 없습니다." });
      }

      return res.json({ success: true, data: row });
    } catch (error) {
      console.error("결재 유형 상세 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 유형 상세 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 유형 생성
  static async createDefinition(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const {
        definition_name,
        definition_name_eng,
        description,
        default_template_id,
        max_steps = 5,
        allow_self_approval = false,
        allow_cancel = true,
        is_active = "Y",
      } = req.body;

      if (!definition_name) {
        return res.status(400).json({ success: false, message: "결재 유형명은 필수입니다." });
      }

      const userId = req.user?.userId || "system";
      const [row] = await query<any>(
        `INSERT INTO approval_definitions (
          definition_name, definition_name_eng, description, default_template_id,
          max_steps, allow_self_approval, allow_cancel, is_active,
          company_code, created_by, updated_by
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $10)
        RETURNING *`,
        [
          definition_name, definition_name_eng, description, default_template_id,
          max_steps, allow_self_approval, allow_cancel, is_active,
          companyCode, userId,
        ]
      );

      return res.status(201).json({ success: true, data: row, message: "결재 유형이 생성되었습니다." });
    } catch (error) {
      console.error("결재 유형 생성 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 유형 생성 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 유형 수정
  static async updateDefinition(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      const existing = await queryOne<any>(
        "SELECT definition_id FROM approval_definitions WHERE definition_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      if (!existing) {
        return res.status(404).json({ success: false, message: "결재 유형을 찾을 수 없습니다." });
      }

      const {
        definition_name, definition_name_eng, description, default_template_id,
        max_steps, allow_self_approval, allow_cancel, is_active,
      } = req.body;

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

      if (definition_name !== undefined) { fields.push(`definition_name = $${idx++}`); params.push(definition_name); }
      if (definition_name_eng !== undefined) { fields.push(`definition_name_eng = $${idx++}`); params.push(definition_name_eng); }
      if (description !== undefined) { fields.push(`description = $${idx++}`); params.push(description); }
      if (default_template_id !== undefined) { fields.push(`default_template_id = $${idx++}`); params.push(default_template_id); }
      if (max_steps !== undefined) { fields.push(`max_steps = $${idx++}`); params.push(max_steps); }
      if (allow_self_approval !== undefined) { fields.push(`allow_self_approval = $${idx++}`); params.push(allow_self_approval); }
      if (allow_cancel !== undefined) { fields.push(`allow_cancel = $${idx++}`); params.push(allow_cancel); }
      if (is_active !== undefined) { fields.push(`is_active = $${idx++}`); params.push(is_active); }

      fields.push(`updated_by = $${idx++}`, `updated_at = NOW()`);
      params.push(req.user?.userId || "system");

      // WHERE 절 파라미터 인덱스를 미리 계산 (쿼리 문자열 내 idx++ 호출 순서 보장)
      const idIdx = idx++;
      const ccIdx = idx++;
      params.push(id, companyCode);

      const [row] = await query<any>(
        `UPDATE approval_definitions SET ${fields.join(", ")}
         WHERE definition_id = $${idIdx} AND company_code = $${ccIdx} RETURNING *`,
        params
      );

      return res.json({ success: true, data: row, message: "결재 유형이 수정되었습니다." });
    } catch (error) {
      console.error("결재 유형 수정 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 유형 수정 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 유형 삭제
  static async deleteDefinition(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      const existing = await queryOne<any>(
        "SELECT definition_id FROM approval_definitions WHERE definition_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      if (!existing) {
        return res.status(404).json({ success: false, message: "결재 유형을 찾을 수 없습니다." });
      }

      await query<any>(
        "DELETE FROM approval_definitions WHERE definition_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      return res.json({ success: true, message: "결재 유형이 삭제되었습니다." });
    } catch (error) {
      console.error("결재 유형 삭제 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 유형 삭제 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }
}

// ============================================================
// 결재선 템플릿 (Approval Line Templates) CRUD
// ============================================================

export class ApprovalTemplateController {
  // 템플릿 목록 조회
  static async getTemplates(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { definition_id, is_active } = req.query;

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

      // SUPER_ADMIN은 전체 조회, 일반 회사는 자사 데이터만
      if (companyCode !== "*") {
        conditions.push(`t.company_code = $${idx++}`);
        params.push(companyCode);
      }

      if (definition_id) {
        conditions.push(`t.definition_id = $${idx++}`);
        params.push(definition_id);
      }
      if (is_active) {
        conditions.push(`t.is_active = $${idx++}`);
        params.push(is_active);
      }

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

      const rows = await query<any>(
        `SELECT t.*, d.definition_name
         FROM approval_line_templates t
         LEFT JOIN approval_definitions d ON t.definition_id = d.definition_id AND t.company_code = d.company_code
         ${whereClause}
         ORDER BY t.company_code, t.template_id ASC`,
        params
      );

      return res.json({ success: true, data: rows });
    } catch (error) {
      console.error("결재선 템플릿 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재선 템플릿 목록 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 템플릿 상세 조회 (단계 포함)
  static async getTemplate(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      // SUPER_ADMIN은 company_code 필터 없이 조회 가능
      const template = await queryOne<any>(
        companyCode === "*"
          ? `SELECT t.*, d.definition_name
             FROM approval_line_templates t
             LEFT JOIN approval_definitions d ON t.definition_id = d.definition_id AND t.company_code = d.company_code
             WHERE t.template_id = $1`
          : `SELECT t.*, d.definition_name
             FROM approval_line_templates t
             LEFT JOIN approval_definitions d ON t.definition_id = d.definition_id AND t.company_code = d.company_code
             WHERE t.template_id = $1 AND t.company_code = $2`,
        companyCode === "*" ? [id] : [id, companyCode]
      );

      if (!template) {
        return res.status(404).json({ success: false, message: "결재선 템플릿을 찾을 수 없습니다." });
      }

      const steps = await query<any>(
        companyCode === "*"
          ? "SELECT * FROM approval_line_template_steps WHERE template_id = $1 ORDER BY step_order ASC"
          : "SELECT * FROM approval_line_template_steps WHERE template_id = $1 AND company_code = $2 ORDER BY step_order ASC",
        companyCode === "*" ? [id] : [id, companyCode]
      );

      return res.json({ success: true, data: { ...template, steps } });
    } catch (error) {
      console.error("결재선 템플릿 상세 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재선 템플릿 상세 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 템플릿 생성 (단계 포함 트랜잭션)
  static async createTemplate(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { template_name, description, definition_id, after_approval_flow_id, is_active = "Y", steps = [] } = req.body;

      if (!template_name) {
        return res.status(400).json({ success: false, message: "템플릿명은 필수입니다." });
      }

      const userId = req.user?.userId || "system";

      let result: any;
      await transaction(async (client) => {
        const { rows } = await client.query(
          `INSERT INTO approval_line_templates (template_name, description, definition_id, after_approval_flow_id, is_active, company_code, created_by, updated_by)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $7) RETURNING *`,
          [template_name, description, definition_id, after_approval_flow_id || null, is_active, companyCode, userId]
        );
        result = rows[0];

        // 단계 일괄 삽입
        if (Array.isArray(steps) && steps.length > 0) {
          for (const step of steps) {
            await client.query(
              `INSERT INTO approval_line_template_steps
               (template_id, step_order, approver_type, approver_user_id, approver_position, approver_dept_code, approver_label, company_code)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
              [
                result.template_id,
                step.step_order,
                step.approver_type || "user",
                step.approver_user_id || null,
                step.approver_position || null,
                step.approver_dept_code || null,
                step.approver_label || null,
                companyCode,
              ]
            );
          }
        }
      });

      return res.status(201).json({ success: true, data: result, message: "결재선 템플릿이 생성되었습니다." });
    } catch (error) {
      console.error("결재선 템플릿 생성 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재선 템플릿 생성 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 템플릿 수정
  static async updateTemplate(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      const existing = await queryOne<any>(
        "SELECT template_id FROM approval_line_templates WHERE template_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      if (!existing) {
        return res.status(404).json({ success: false, message: "결재선 템플릿을 찾을 수 없습니다." });
      }

      const { template_name, description, definition_id, after_approval_flow_id, is_active, steps } = req.body;
      const userId = req.user?.userId || "system";

      let result: any;
      await transaction(async (client) => {
        const fields: string[] = [];
        const params: any[] = [];
        let idx = 1;

        if (template_name !== undefined) { fields.push(`template_name = $${idx++}`); params.push(template_name); }
        if (description !== undefined) { fields.push(`description = $${idx++}`); params.push(description); }
        if (definition_id !== undefined) { fields.push(`definition_id = $${idx++}`); params.push(definition_id); }
        if (after_approval_flow_id !== undefined) { fields.push(`after_approval_flow_id = $${idx++}`); params.push(after_approval_flow_id); }
        if (is_active !== undefined) { fields.push(`is_active = $${idx++}`); params.push(is_active); }
        fields.push(`updated_by = $${idx++}`, `updated_at = NOW()`);
        params.push(userId);

        // WHERE 절 파라미터 인덱스를 미리 계산
        const tmplIdx = idx++;
        const ccIdx = idx++;
        params.push(id, companyCode);

        const { rows } = await client.query(
          `UPDATE approval_line_templates SET ${fields.join(", ")}
           WHERE template_id = $${tmplIdx} AND company_code = $${ccIdx} RETURNING *`,
          params
        );
        result = rows[0];

        // 단계 재등록 (steps 배열이 주어진 경우 전체 교체)
        if (Array.isArray(steps)) {
          await client.query(
            "DELETE FROM approval_line_template_steps WHERE template_id = $1 AND company_code = $2",
            [id, companyCode]
          );
          for (const step of steps) {
            await client.query(
              `INSERT INTO approval_line_template_steps
               (template_id, step_order, approver_type, approver_user_id, approver_position, approver_dept_code, approver_label, company_code)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
              [id, step.step_order, step.approver_type || "user", step.approver_user_id || null,
               step.approver_position || null, step.approver_dept_code || null, step.approver_label || null, companyCode]
            );
          }
        }
      });

      return res.json({ success: true, data: result, message: "결재선 템플릿이 수정되었습니다." });
    } catch (error) {
      console.error("결재선 템플릿 수정 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재선 템플릿 수정 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 템플릿 삭제
  static async deleteTemplate(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      const existing = await queryOne<any>(
        "SELECT template_id FROM approval_line_templates WHERE template_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      if (!existing) {
        return res.status(404).json({ success: false, message: "결재선 템플릿을 찾을 수 없습니다." });
      }

      await query<any>(
        "DELETE FROM approval_line_templates WHERE template_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      return res.json({ success: true, message: "결재선 템플릿이 삭제되었습니다." });
    } catch (error) {
      console.error("결재선 템플릿 삭제 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재선 템플릿 삭제 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }
}

// ============================================================
// 다음 step 활성화 헬퍼 (혼합형 결재선 대응)
// notification step은 자동 통과 후 재귀적으로 다음 step 진행
// ============================================================

// 결재 상태 변경 시 원본 테이블(target_table)의 approval_status를 동기화하는 범용 hook
async function syncApprovalStatusToTarget(
  client: PoolClient,
  requestId: number,
  newStatus: string,
  companyCode: string,
): Promise<void> {
  try {
    const { rows: [req] } = await client.query(
      `SELECT target_table, target_record_id FROM approval_requests WHERE request_id = $1 AND company_code = $2`,
      [requestId, companyCode],
    );
    if (!req?.target_table || !req?.target_record_id || req.target_record_id === "0") return;

    const { rows: cols } = await client.query(
      `SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = 'approval_status'`,
      [req.target_table],
    );
    if (cols.length === 0) return;

    const statusMap: Record<string, string> = {
      in_progress: "결재중",
      approved: "결재완료",
      rejected: "반려",
      cancelled: "작성중",
      draft: "작성중",
      post_pending: "후결대기",
    };

    const businessStatus = statusMap[newStatus] || newStatus;
    const safeTable = req.target_table.replace(/[^a-zA-Z0-9_]/g, "");

    // super admin(company_code='*')은 다른 회사 레코드도 업데이트 가능
    if (companyCode === "*") {
      await client.query(
        `UPDATE "${safeTable}" SET approval_status = $1 WHERE id = $2`,
        [businessStatus, req.target_record_id],
      );
    } else {
      await client.query(
        `UPDATE "${safeTable}" SET approval_status = $1 WHERE id = $2 AND company_code = $3`,
        [businessStatus, req.target_record_id, companyCode],
      );
    }

    // 결재 완료(approved) 시 제어관리(노드 플로우) 자동 실행
    if (newStatus === "approved") {
      await executeAfterApprovalFlow(client, requestId, companyCode, req);
    }
  } catch (err) {
    console.error("[syncApprovalStatusToTarget] 원본 테이블 상태 동기화 실패:", err);
  }
}

// 결재 완료 후 제어관리(노드 플로우) 실행 hook
// 우선순위: 템플릿(template) > 정의(definition) > 요청(request) 직접 지정
async function executeAfterApprovalFlow(
  client: PoolClient,
  requestId: number,
  companyCode: string,
  approvalReq: { target_table: string; target_record_id: string },
): Promise<void> {
  try {
    const { rows: [reqData] } = await client.query(
      `SELECT r.after_approval_flow_id, r.definition_id, r.template_id, r.title, r.requester_id
       FROM approval_requests r WHERE r.request_id = $1`,
      [requestId],
    );

    let flowId: number | null = null;

    // 1순위: 템플릿에 연결된 제어관리 플로우
    if (reqData?.template_id) {
      const { rows: [tmpl] } = await client.query(
        `SELECT after_approval_flow_id FROM approval_line_templates WHERE template_id = $1`,
        [reqData.template_id],
      );
      flowId = tmpl?.after_approval_flow_id || null;
    }

    // 2순위: 정의(definition)에 연결된 제어관리 플로우 (fallback)
    if (!flowId && reqData?.definition_id) {
      const { rows: [def] } = await client.query(
        `SELECT after_approval_flow_id FROM approval_definitions WHERE definition_id = $1`,
        [reqData.definition_id],
      );
      flowId = def?.after_approval_flow_id || null;
    }

    // 3순위: 요청 자체에 직접 지정된 플로우
    if (!flowId) {
      flowId = reqData?.after_approval_flow_id || null;
    }

    if (!flowId) return;

    // 3. 원본 레코드 데이터 조회
    const safeTable = approvalReq.target_table.replace(/[^a-zA-Z0-9_]/g, "");
    const { rows: [targetRecord] } = await client.query(
      `SELECT * FROM "${safeTable}" WHERE id = $1`,
      [approvalReq.target_record_id],
    );

    // 4. 노드 플로우 실행
    console.log(`[제어관리] 결재 완료 후 플로우 #${flowId} 실행 (request_id=${requestId})`);
    const result = await NodeFlowExecutionService.executeFlow(flowId, {
      formData: targetRecord || {},
      approvalInfo: {
        requestId,
        title: reqData.title,
        requesterId: reqData.requester_id,
        targetTable: approvalReq.target_table,
        targetRecordId: approvalReq.target_record_id,
      },
      companyCode,
      selectedRows: targetRecord ? [targetRecord] : [],
    });

    console.log(`[제어관리] 플로우 #${flowId} 실행 결과: ${result.success ? "성공" : "실패"} (${result.executionTime}ms)`);
  } catch (err) {
    // 제어관리 실패는 결재 승인 자체에 영향 주지 않음
    console.error("[executeAfterApprovalFlow] 제어관리 실행 실패:", err);
  }
}

async function activateNextStep(
  client: PoolClient,
  requestId: number,
  currentStep: number,
  totalSteps: number,
  companyCode: string,
  userId: string,
  comment: string | null,
): Promise<void> {
  const nextStep = currentStep + 1;

  if (nextStep > totalSteps) {
    // 최종 승인 처리
    await client.query(
      `UPDATE approval_requests
       SET status = CASE WHEN approval_type = 'post' THEN 'approved' ELSE 'approved' END,
           is_post_approved = CASE WHEN approval_type = 'post' THEN true ELSE is_post_approved END,
           post_approved_at = CASE WHEN approval_type = 'post' THEN NOW() ELSE post_approved_at END,
           final_approver_id = $1, final_comment = $2, completed_at = NOW(), updated_at = NOW()
       WHERE request_id = $3 AND company_code = $4`,
      [userId, comment, requestId, companyCode]
    );
    await syncApprovalStatusToTarget(client, requestId, "approved", companyCode);
    return;
  }

  // 다음 step의 결재 라인 조회 (FOR UPDATE로 동시성 방어)
  const { rows: nextLines } = await client.query(
    `SELECT * FROM approval_lines
     WHERE request_id = $1 AND step_order = $2 AND company_code = $3
     FOR UPDATE`,
    [requestId, nextStep, companyCode]
  );

  if (nextLines.length === 0) {
    // 다음 step이 비어있으면 최종 승인 처리
    await client.query(
      `UPDATE approval_requests
       SET status = 'approved', final_approver_id = $1, final_comment = $2,
           completed_at = NOW(), updated_at = NOW()
       WHERE request_id = $3 AND company_code = $4`,
      [userId, comment, requestId, companyCode]
    );
    await syncApprovalStatusToTarget(client, requestId, "approved", companyCode);
    return;
  }

  const nextStepType = nextLines[0].step_type || "approval";

  if (nextStepType === "notification") {
    // 통보 단계: 자동 approved 처리 후 다음 step으로 재귀
    for (const nl of nextLines) {
      await client.query(
        `UPDATE approval_lines SET status = 'approved', comment = '자동 통보 처리', processed_at = NOW()
         WHERE line_id = $1 AND company_code = $2`,
        [nl.line_id, companyCode]
      );
    }
    await client.query(
      `UPDATE approval_requests SET current_step = $1, updated_at = NOW()
       WHERE request_id = $2 AND company_code = $3`,
      [nextStep, requestId, companyCode]
    );
    // 재귀: 통보 다음 step 활성화
    await activateNextStep(client, requestId, nextStep, totalSteps, companyCode, userId, comment);
  } else {
    // approval 또는 consensus: pending으로 전환
    await client.query(
      `UPDATE approval_lines SET status = 'pending'
       WHERE request_id = $1 AND step_order = $2 AND company_code = $3`,
      [requestId, nextStep, companyCode]
    );
    await client.query(
      `UPDATE approval_requests SET current_step = $1, updated_at = NOW()
       WHERE request_id = $2 AND company_code = $3`,
      [nextStep, requestId, companyCode]
    );
  }
}

// ============================================================
// 결재 요청 (Approval Requests) CRUD
// ============================================================

export class ApprovalRequestController {
  // 결재 요청 목록 조회
  static async getRequests(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      const userId = req.user?.userId;
      if (!companyCode || !userId) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { status, target_table, target_record_id, requester_id, my_approvals, page = "1", limit = "20" } = req.query;

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

      // SUPER_ADMIN은 전체 조회, 일반 회사는 자사 데이터만
      if (companyCode !== "*") {
        conditions.push(`r.company_code = $${idx++}`);
        params.push(companyCode);
      }

      if (status) {
        conditions.push(`r.status = $${idx++}`);
        params.push(status);
      }
      if (target_table) {
        conditions.push(`r.target_table = $${idx++}`);
        params.push(target_table);
      }
      if (target_record_id) {
        conditions.push(`r.target_record_id = $${idx++}`);
        params.push(target_record_id);
      }
      if (requester_id) {
        conditions.push(`r.requester_id = $${idx++}`);
        params.push(requester_id);
      }

      // 내 결재 대기 목록: 현재 사용자가 결재자인 라인만 조회
      if (my_approvals === "true") {
        conditions.push(
          `EXISTS (SELECT 1 FROM approval_lines l WHERE l.request_id = r.request_id AND l.approver_id = $${idx++} AND l.status = 'pending' AND l.company_code = r.company_code)`
        );
        params.push(userId);
      }

      const offset = (parseInt(page as string) - 1) * parseInt(limit as string);
      const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";

      // countParams는 WHERE 조건 파라미터만 포함 (LIMIT/OFFSET 제외)
      // my_approvals 파라미터도 포함된 후 복사해야 함
      const countParams = [...params];
      const [countRow] = await query<any>(
        `SELECT COUNT(*) as total FROM approval_requests r ${whereClause}`,
        countParams
      );

      // LIMIT/OFFSET 파라미터 인덱스를 미리 계산 (countParams 복사 후에 idx 증가)
      const limitIdx = idx++;
      const offsetIdx = idx++;
      params.push(parseInt(limit as string), offset);

      const rows = await query<any>(
        `SELECT r.*, d.definition_name
         FROM approval_requests r
         LEFT JOIN approval_definitions d ON r.definition_id = d.definition_id AND r.company_code = d.company_code
         ${whereClause}
         ORDER BY r.created_at DESC
         LIMIT $${limitIdx} OFFSET $${offsetIdx}`,
        params
      );

      return res.json({
        success: true,
        data: rows,
        total: parseInt(countRow?.total || "0"),
        page: parseInt(page as string),
        limit: parseInt(limit as string),
      });
    } catch (error) {
      console.error("결재 요청 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 요청 목록 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 요청 상세 조회 (라인 포함)
  static async getRequest(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      // SUPER_ADMIN은 company_code 필터 없이 모든 요청 조회 가능
      const request = await queryOne<any>(
        companyCode === "*"
          ? `SELECT r.*, d.definition_name
             FROM approval_requests r
             LEFT JOIN approval_definitions d ON r.definition_id = d.definition_id AND r.company_code = d.company_code
             WHERE r.request_id = $1`
          : `SELECT r.*, d.definition_name
             FROM approval_requests r
             LEFT JOIN approval_definitions d ON r.definition_id = d.definition_id AND r.company_code = d.company_code
             WHERE r.request_id = $1 AND r.company_code = $2`,
        companyCode === "*" ? [id] : [id, companyCode]
      );

      if (!request) {
        return res.status(404).json({ success: false, message: "결재 요청을 찾을 수 없습니다." });
      }

      const lines = await query<any>(
        companyCode === "*"
          ? "SELECT * FROM approval_lines WHERE request_id = $1 ORDER BY step_order ASC"
          : "SELECT * FROM approval_lines WHERE request_id = $1 AND company_code = $2 ORDER BY step_order ASC",
        companyCode === "*" ? [id] : [id, companyCode]
      );

      return res.json({ success: true, data: { ...request, lines } });
    } catch (error) {
      console.error("결재 요청 상세 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 요청 상세 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 요청 생성 (혼합형 결재선 지원 - self/escalation/consensus/post)
  static async createRequest(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      if (!companyCode) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const {
        title, description, definition_id, template_id, target_table, target_record_id,
        target_record_data, screen_id, button_component_id,
        approvers,
        approval_mode,
        approval_type = "escalation",
      } = req.body;

      if (!title || !target_table) {
        return res.status(400).json({ success: false, message: "제목과 대상 테이블은 필수입니다." });
      }

      // target_record_id는 NOT NULL 컬럼이므로 빈 값은 기본값으로 대체
      const safeTargetRecordId = target_record_id || "0";

      const userId = req.user?.userId || "system";
      const userName = req.user?.userName || "";
      const deptName = req.user?.deptName || "";

      // approval_mode를 target_record_data에 병합 저장 (하위호환)
      const mergedRecordData = {
        ...(target_record_data || {}),
        approval_mode: approval_mode || "sequential",
      };

      // ========== 자기결재(전결) ==========
      if (approval_type === "self") {
        // definition_id가 있으면 allow_self_approval 체크
        if (definition_id) {
          const def = await queryOne<any>(
            "SELECT allow_self_approval FROM approval_definitions WHERE definition_id = $1 AND company_code = $2",
            [definition_id, companyCode]
          );
          if (def && !def.allow_self_approval) {
            return res.status(400).json({ success: false, message: "해당 결재 유형은 자기결재(전결)를 허용하지 않습니다." });
          }
        }

        let result: any;
        await transaction(async (client) => {
          const { rows: reqRows } = await client.query(
            `INSERT INTO approval_requests (
              title, description, definition_id, template_id, target_table, target_record_id,
              target_record_data, status, current_step, total_steps, approval_type,
              requester_id, requester_name, requester_dept,
              screen_id, button_component_id, company_code,
              final_approver_id, completed_at
            ) VALUES ($1, $2, $3, $4, $5, $6, $7, 'approved', 1, 1, 'self',
                      $8, $9, $10, $11, $12, $13, $8, NOW())
            RETURNING *`,
            [
              title, description, definition_id, template_id || null, target_table, safeTargetRecordId,
              JSON.stringify(mergedRecordData),
              userId, userName, deptName,
              screen_id, button_component_id, companyCode,
            ]
          );
          result = reqRows[0];

          // 본인을 결재자로 INSERT (이미 approved)
          await client.query(
            `INSERT INTO approval_lines (
              request_id, step_order, approver_id, approver_name, approver_position,
              approver_dept, approver_label, status, step_type, processed_at, company_code
            ) VALUES ($1, 1, $2, $3, $4, $5, '자기결재', 'approved', 'approval', NOW(), $6)`,
            [result.request_id, userId, userName, req.user?.positionName || null, deptName, companyCode]
          );

          await syncApprovalStatusToTarget(client, result.request_id, "approved", companyCode);
        });

        return res.status(201).json({ success: true, data: result, message: "자기결재(전결) 처리되었습니다." });
      }

      // ========== 그 외 유형: approvers 필수 검증 ==========
      if (!Array.isArray(approvers) || approvers.length === 0) {
        return res.status(400).json({ success: false, message: "결재자를 1명 이상 지정해야 합니다." });
      }

      // 각 approver에 step_type/step_order 할당 (혼합형 지원)
      const hasExplicitStepType = approvers.some((a: any) => a.step_type);

      interface NormalizedApprover {
        approver_id: string;
        approver_name: string | null;
        approver_position: string | null;
        approver_dept: string | null;
        approver_label: string | null;
        step_order: number;
        step_type: string;
      }

      let normalizedApprovers: NormalizedApprover[];

      if (approval_type === "consensus" && !hasExplicitStepType) {
        // 단순 합의결재: 전원 step_order=1, step_type='consensus'
        normalizedApprovers = approvers.map((a: any) => ({
          approver_id: a.approver_id,
          approver_name: a.approver_name || null,
          approver_position: a.approver_position || null,
          approver_dept: a.approver_dept || null,
          approver_label: a.approver_label || "합의 결재",
          step_order: 1,
          step_type: "consensus",
        }));
      } else if (hasExplicitStepType) {
        // 혼합형: 각 approver에 명시된 step_type/step_order 사용
        normalizedApprovers = approvers.map((a: any, i: number) => ({
          approver_id: a.approver_id,
          approver_name: a.approver_name || null,
          approver_position: a.approver_position || null,
          approver_dept: a.approver_dept || null,
          approver_label: a.approver_label || null,
          step_order: a.step_order ?? (i + 1),
          step_type: a.step_type || "approval",
        }));
      } else {
        // escalation / post: 기본 sequential
        normalizedApprovers = approvers.map((a: any, i: number) => ({
          approver_id: a.approver_id,
          approver_name: a.approver_name || null,
          approver_position: a.approver_position || null,
          approver_dept: a.approver_dept || null,
          approver_label: a.approver_label || `${i + 1}차 결재`,
          step_order: a.step_order ?? (i + 1),
          step_type: "approval",
        }));
      }

      // escalation 타입에서 같은 step_order에 2명 이상이면서 step_type이 approval인 경우 에러
      const stepOrderGroups = new Map<number, NormalizedApprover[]>();
      for (const a of normalizedApprovers) {
        const group = stepOrderGroups.get(a.step_order) || [];
        group.push(a);
        stepOrderGroups.set(a.step_order, group);
      }
      for (const [stepOrder, group] of stepOrderGroups) {
        if (group.length > 1) {
          const allApproval = group.every(g => g.step_type === "approval");
          if (allApproval) {
            return res.status(400).json({
              success: false,
              message: `step_order ${stepOrder}에 approval 타입 결재자가 2명 이상입니다. consensus로 지정해주세요.`,
            });
          }
        }
      }

      // total_steps = 고유한 step_order의 최대값
      const uniqueStepOrders = [...new Set(normalizedApprovers.map(a => a.step_order))].sort((a, b) => a - b);
      const totalSteps = Math.max(...uniqueStepOrders);

      // 저장할 approval_type 결정 (혼합형은 escalation으로 저장)
      const storedApprovalType = hasExplicitStepType ? "escalation" : approval_type;
      const initialStatus = approval_type === "post" ? "post_pending" : "requested";

      let result: any;
      await transaction(async (client) => {
        const { rows: reqRows } = await client.query(
          `INSERT INTO approval_requests (
            title, description, definition_id, template_id, target_table, target_record_id,
            target_record_data, status, current_step, total_steps, approval_type,
            requester_id, requester_name, requester_dept,
            screen_id, button_component_id, company_code
          ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 1, $9, $10, $11, $12, $13, $14, $15, $16)
          RETURNING *`,
          [
            title, description, definition_id, template_id || null, target_table, safeTargetRecordId,
            JSON.stringify(mergedRecordData), initialStatus, totalSteps, storedApprovalType,
            userId, userName, deptName,
            screen_id, button_component_id, companyCode,
          ]
        );
        result = reqRows[0];

        const firstStep = uniqueStepOrders[0];

        for (const approver of normalizedApprovers) {
          // 첫 번째 step의 결재자만 pending, 나머지는 waiting
          let lineStatus: string;
          if (approver.step_order === firstStep) {
            lineStatus = "pending";
          } else {
            lineStatus = "waiting";
          }

          await client.query(
            `INSERT INTO approval_lines (
              request_id, step_order, approver_id, approver_name, approver_position,
              approver_dept, approver_label, status, step_type, company_code
            ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
            [
              result.request_id,
              approver.step_order,
              approver.approver_id,
              approver.approver_name,
              approver.approver_position,
              approver.approver_dept,
              approver.approver_label,
              lineStatus,
              approver.step_type,
              companyCode,
            ]
          );
        }

        // 첫 번째 step이 notification이면 자동 통과 처리
        const firstStepLines = normalizedApprovers.filter(a => a.step_order === firstStep);
        const firstStepType = firstStepLines[0]?.step_type;

        if (firstStepType === "notification") {
          // notification은 자동 처리 → activateNextStep으로 재귀
          for (const nl of firstStepLines) {
            await client.query(
              `UPDATE approval_lines SET status = 'approved', comment = '자동 통보 처리', processed_at = NOW()
               WHERE request_id = $1 AND step_order = $2 AND approver_id = $3 AND company_code = $4`,
              [result.request_id, nl.step_order, nl.approver_id, companyCode]
            );
          }
          await activateNextStep(client, result.request_id, firstStep, totalSteps, companyCode, userId, null);
        }

        // status를 in_progress로 업데이트 (post_pending 제외)
        if (approval_type !== "post") {
          await client.query(
            `UPDATE approval_requests SET status = 'in_progress' WHERE request_id = $1 AND company_code = $2`,
            [result.request_id, companyCode]
          );
          result.status = "in_progress";
          await syncApprovalStatusToTarget(client, result.request_id, "in_progress", companyCode);
        } else {
          await syncApprovalStatusToTarget(client, result.request_id, "post_pending", companyCode);
        }
      });

      return res.status(201).json({ success: true, data: result, message: "결재 요청이 생성되었습니다." });
    } catch (error) {
      console.error("결재 요청 생성 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 요청 생성 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 결재 요청 회수 (cancel)
  static async cancelRequest(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      const userId = req.user?.userId;
      if (!companyCode || !userId) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      const request = await queryOne<any>(
        "SELECT * FROM approval_requests WHERE request_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      if (!request) {
        return res.status(404).json({ success: false, message: "결재 요청을 찾을 수 없습니다." });
      }

      if (request.requester_id !== userId) {
        return res.status(403).json({ success: false, message: "본인이 요청한 건만 회수할 수 있습니다." });
      }

      if (!["requested", "in_progress", "post_pending"].includes(request.status)) {
        return res.status(400).json({ success: false, message: "이미 처리된 결재 요청은 회수할 수 없습니다." });
      }

      await transaction(async (client) => {
        await client.query(
          "UPDATE approval_requests SET status = 'cancelled', updated_at = NOW() WHERE request_id = $1 AND company_code = $2",
          [id, companyCode]
        );
        await syncApprovalStatusToTarget(client, Number(id), "cancelled", companyCode);
      });

      return res.json({ success: true, message: "결재 요청이 회수되었습니다." });
    } catch (error) {
      console.error("결재 요청 회수 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 요청 회수 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 후결 처리 엔드포인트
  static async postApprove(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      const userId = req.user?.userId;
      if (!companyCode || !userId) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { id } = req.params;
      const { comment } = req.body;

      const request = await queryOne<any>(
        "SELECT * FROM approval_requests WHERE request_id = $1 AND company_code = $2",
        [id, companyCode]
      );

      if (!request) {
        return res.status(404).json({ success: false, message: "결재 요청을 찾을 수 없습니다." });
      }

      if (request.approval_type !== "post") {
        return res.status(400).json({ success: false, message: "후결 유형의 결재 요청만 후결 처리할 수 있습니다." });
      }

      if (request.is_post_approved) {
        return res.status(400).json({ success: false, message: "이미 후결 처리된 요청입니다." });
      }

      // 결재선 전원 approved 확인
      const [pendingCount] = await query<any>(
        `SELECT COUNT(*) as cnt FROM approval_lines
         WHERE request_id = $1 AND status NOT IN ('approved', 'skipped') AND company_code = $2`,
        [id, companyCode]
      );

      if (parseInt(pendingCount?.cnt || "0") > 0) {
        return res.status(400).json({ success: false, message: "모든 결재자의 승인이 완료되지 않았습니다." });
      }

      await transaction(async (client) => {
        await client.query(
          `UPDATE approval_requests
           SET status = 'approved', is_post_approved = true, post_approved_at = NOW(),
               final_approver_id = $1, final_comment = $2, completed_at = NOW(), updated_at = NOW()
           WHERE request_id = $3 AND company_code = $4`,
          [userId, comment || null, id, companyCode]
        );
        await syncApprovalStatusToTarget(client, Number(id), "approved", companyCode);
      });

      return res.json({ success: true, message: "후결 처리가 완료되었습니다." });
    } catch (error) {
      console.error("후결 처리 오류:", error);
      return res.status(500).json({
        success: false,
        message: "후결 처리 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }
}

// ============================================================
// 결재 라인 처리 (Approval Lines - 승인/반려)
// ============================================================

export class ApprovalLineController {
  // 결재 처리 (승인/반려) - FOR UPDATE 동시성 방어 + 대결 + step_type 분기
  static async processApproval(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      const userId = req.user?.userId;
      if (!companyCode || !userId) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const { lineId } = req.params;
      const { action, comment, proxy_reason } = req.body;

      if (!["approved", "rejected"].includes(action)) {
        return res.status(400).json({ success: false, message: "액션은 approved 또는 rejected여야 합니다." });
      }

      await transaction(async (client) => {
        // FOR UPDATE로 결재 라인 잠금
        // super admin(*)은 모든 회사의 라인을 처리할 수 있음
        const lineQuery = companyCode === "*"
          ? `SELECT * FROM approval_lines WHERE line_id = $1 FOR UPDATE`
          : `SELECT * FROM approval_lines WHERE line_id = $1 AND company_code IN ($2, '*') FOR UPDATE`;
        const lineParams = companyCode === "*" ? [lineId] : [lineId, companyCode];
        const { rows: [line] } = await client.query(lineQuery, lineParams);

        if (!line) {
          throw new ValidationError(404, "결재 라인을 찾을 수 없습니다.");
        }

        if (line.status !== "pending") {
          throw new ValidationError(400, "대기 중인 결재만 처리할 수 있습니다.");
        }

        // 대결(proxy) 인증 로직
        let proxyFor: string | null = null;
        let proxyReasonVal: string | null = null;

        if (line.approver_id !== userId) {
          // super admin(company_code='*')은 모든 결재를 대리 처리 가능
          if (companyCode === "*") {
            proxyFor = line.approver_id;
            proxyReasonVal = proxy_reason || "최고관리자 대리 처리";
          } else {
            const { rows: proxyRows } = await client.query(
              `SELECT * FROM approval_proxy_settings
               WHERE original_user_id = $1 AND proxy_user_id = $2
               AND is_active = 'Y' AND start_date <= CURRENT_DATE AND end_date >= CURRENT_DATE
               AND company_code = $3`,
              [line.approver_id, userId, companyCode]
            );
            if (proxyRows.length === 0) {
              throw new ValidationError(403, "본인이 결재자로 지정된 건만 처리할 수 있습니다.");
            }
            proxyFor = line.approver_id;
            proxyReasonVal = proxy_reason || proxyRows[0].reason || "대결 처리";
          }
        }

        // 현재 라인 처리 (proxy_for, proxy_reason 포함) - 라인의 company_code 기준
        await client.query(
          `UPDATE approval_lines
           SET status = $1, comment = $2, processed_at = NOW(),
               proxy_for = $3, proxy_reason = $4
           WHERE line_id = $5 AND company_code = $6`,
          [action, comment || null, proxyFor, proxyReasonVal, lineId, line.company_code]
        );

        // 결재 요청 조회 (FOR UPDATE) - 라인의 company_code 기준
        const { rows: [request] } = await client.query(
          `SELECT * FROM approval_requests WHERE request_id = $1 AND company_code = $2 FOR UPDATE`,
          [line.request_id, line.company_code]
        );

        if (!request) return;

        const lineCC = line.company_code;

        if (action === "rejected") {
          // 반려: 전체 요청 반려 처리
          await client.query(
            `UPDATE approval_requests SET status = 'rejected', final_approver_id = $1, final_comment = $2,
             completed_at = NOW(), updated_at = NOW()
             WHERE request_id = $3 AND company_code = $4`,
            [userId, comment || null, line.request_id, lineCC]
          );
          // 남은 pending/waiting 라인도 skipped 처리
          await client.query(
            `UPDATE approval_lines SET status = 'skipped'
             WHERE request_id = $1 AND status IN ('pending', 'waiting') AND line_id != $2 AND company_code = $3`,
            [line.request_id, lineId, lineCC]
          );
          await syncApprovalStatusToTarget(client, line.request_id, "rejected", lineCC);
        } else {
          // 승인 처리: step_type 기반 분기
          const currentStepType = line.step_type || "approval";

          // 기존 isParallelMode 하위호환 (step_type이 없는 기존 데이터)
          const recordData = request.target_record_data;
          const isLegacyParallel = recordData?.approval_mode === "parallel" && !line.step_type;

          if (isLegacyParallel) {
            // 레거시 동시결재 (하위호환)
            const { rows: remainingLines } = await client.query(
              `SELECT COUNT(*) as cnt FROM approval_lines
               WHERE request_id = $1 AND status = 'pending' AND line_id != $2 AND company_code = $3`,
              [line.request_id, lineId, lineCC]
            );
            const remaining = parseInt(remainingLines[0]?.cnt || "0");

            if (remaining === 0) {
              await client.query(
                `UPDATE approval_requests SET status = 'approved', final_approver_id = $1, final_comment = $2,
                 completed_at = NOW(), updated_at = NOW()
                 WHERE request_id = $3 AND company_code = $4`,
                [userId, comment || null, line.request_id, lineCC]
              );
              await syncApprovalStatusToTarget(client, line.request_id, "approved", lineCC);
            }
          } else if (currentStepType === "consensus") {
            // 합의결재: 같은 step의 모든 결재자 승인 확인
            const { rows: remaining } = await client.query(
              `SELECT COUNT(*) as cnt FROM approval_lines
               WHERE request_id = $1 AND step_order = $2
               AND status NOT IN ('approved', 'skipped')
               AND line_id != $3 AND company_code = $4`,
              [line.request_id, line.step_order, lineId, lineCC]
            );

            if (parseInt(remaining[0].cnt) === 0) {
              // 합의 완료 → 다음 step 활성화
              await activateNextStep(
                client, line.request_id, line.step_order, request.total_steps,
                lineCC, userId, comment || null,
              );
            }
          } else {
            // approval (기존 sequential 로직): 다음 step 활성화
            await activateNextStep(
              client, line.request_id, line.step_order, request.total_steps,
              lineCC, userId, comment || null,
            );
          }
        }
      });

      return res.json({ success: true, message: action === "approved" ? "승인 처리되었습니다." : "반려 처리되었습니다." });
    } catch (error) {
      // ValidationError는 트랜잭션이 rollback된 후 적절한 HTTP 상태코드로 응답
      if (error instanceof Error && error.name === "ValidationError") {
        const validationErr = error as any;
        return res.status(validationErr.statusCode).json({
          success: false,
          message: validationErr.message,
        });
      }
      console.error("결재 처리 오류:", error);
      return res.status(500).json({
        success: false,
        message: "결재 처리 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  // 내 결재 대기 목록 조회
  static async getMyPendingLines(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;
      const userId = req.user?.userId;
      if (!companyCode || !userId) {
        return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
      }

      const rows = await query<any>(
        `SELECT l.*, r.title, r.target_table, r.target_record_id, r.requester_name, r.requester_dept, r.created_at as request_created_at
         FROM approval_lines l
         JOIN approval_requests r ON l.request_id = r.request_id AND l.company_code = r.company_code
         WHERE l.approver_id = $1 AND l.status = 'pending' AND l.company_code IN ($2, '*')
         ORDER BY r.created_at ASC`,
        [userId, companyCode]
      );

      return res.json({ success: true, data: rows });
    } catch (error) {
      console.error("내 결재 대기 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "내 결재 대기 목록 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }
}

