import { Request, Response } from "express";
import { logger } from "../utils/logger";
import { AuthenticatedRequest } from "../types/auth";
import { ApiResponse } from "../types/common";
import { Client } from "pg";
import { query, queryOne, getPool } from "../database/db";
import config from "../config/environment";
import { AdminService } from "../services/adminService";
import { EncryptUtil } from "../utils/encryptUtil";
import { FileSystemManager } from "../utils/fileSystemManager";
import { validateBusinessNumber } from "../utils/businessNumberValidator";
import { MenuCopyService } from "../services/menuCopyService";
import { auditLogService, getClientIp } from "../services/auditLogService";

/**
 * 관리자 메뉴 목록 조회
 */
export async function getAdminMenus(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    // 현재 로그인한 사용자의 정보 가져오기
    const userId = req.user?.userId;
    const userCompanyCode = req.user?.companyCode || "ILSHIN";
    const userType = req.user?.userType;
    const userLang = (req.query.userLang as string) || "ko";
    const menuType = req.query.menuType as string | undefined; // menuType 파라미터 추가
    const includeInactive = req.query.includeInactive === "true"; // includeInactive 파라미터 추가

    const paramMap = {
      userId,
      userCompanyCode,
      userType,
      userLang,
      menuType, // includeInactive와 관계없이 menuType 유지 (관리자/사용자 구분)
      includeInactive, // includeInactive 추가
    };

    const menuList = await AdminService.getAdminMenuList(paramMap);

    const response: ApiResponse<any[]> = {
      success: true,
      message: "관리자 메뉴 목록 조회 성공",
      data: menuList,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("관리자 메뉴 목록 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "관리자 메뉴 목록 조회 중 오류가 발생했습니다.",
      error: {
        code: "ADMIN_MENU_LIST_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 사용자 메뉴 목록 조회
 */
export async function getUserMenus(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    // 현재 로그인한 사용자의 정보 가져오기
    const userId = req.user?.userId;
    const userCompanyCode = req.user?.companyCode || "ILSHIN";
    const userType = req.user?.userType;
    const userLang = (req.query.userLang as string) || "ko";

    const paramMap = {
      userId,
      userCompanyCode,
      userType,
      userLang,
    };

    const menuList = await AdminService.getUserMenuList(paramMap);

    const response: ApiResponse<any[]> = {
      success: true,
      message: "사용자 메뉴 목록 조회 성공",
      data: menuList,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 메뉴 목록 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "사용자 메뉴 목록 조회 중 오류가 발생했습니다.",
      error: {
        code: "USER_MENU_LIST_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * POP 메뉴 목록 조회
 * [POP] 태그가 있는 L1 메뉴의 하위 active 메뉴를 반환
 */
export async function getPopMenus(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const userCompanyCode = req.user?.companyCode || "ILSHIN";
    const userType = req.user?.userType;

    const result = await AdminService.getPopMenuList({
      userCompanyCode,
      userType,
    });

    const response: ApiResponse<any> = {
      success: true,
      message: "POP 메뉴 목록 조회 성공",
      data: result,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("POP 메뉴 목록 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "POP 메뉴 목록 조회 중 오류가 발생했습니다.",
      error: {
        code: "POP_MENU_LIST_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 메뉴 정보 조회
 */
export async function getMenuInfo(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { menuId } = req.params;
    logger.info(`=== 메뉴 정보 조회 시작 - menuId: ${menuId} ===`);

    const menuInfo = await AdminService.getMenuInfo(menuId);

    if (!menuInfo) {
      const response: ApiResponse<null> = {
        success: false,
        message: "메뉴를 찾을 수 없습니다.",
        error: {
          code: "MENU_NOT_FOUND",
          details: `Menu ID: ${menuId}`,
        },
      };
      res.status(404).json(response);
      return;
    }

    const response: ApiResponse<any> = {
      success: true,
      message: "메뉴 정보 조회 성공",
      data: menuInfo,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("메뉴 정보 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "메뉴 정보 조회 중 오류가 발생했습니다.",
      error: {
        code: "MENU_INFO_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * GET /api/admin/users
 * 사용자 목록 조회 API
 * 기존 Java AdminController.getUserList() 포팅
 */
export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
  try {
    logger.info("사용자 목록 조회 요청", {
      query: req.query,
      user: req.user,
    });

    const {
      page = 1,
      countPerPage = 20,
      search,
      searchField,
      searchValue,
      search_sabun,
      search_companyName,
      search_deptName,
      search_positionName,
      search_userId,
      search_userName,
      search_tel,
      search_email,
      deptCode,
      status,
      companyCode, // 회사 코드 필터 추가
      size, // countPerPage 대신 사용 가능
    } = req.query;

    // Raw Query를 사용한 사용자 목록 조회
    let searchType = "none";
    let whereConditions: string[] = [];
    let queryParams: any[] = [];
    let paramIndex = 1;

    // 회사 코드 필터 (권한 그룹 멤버 관리 시 사용)
    if (companyCode && typeof companyCode === "string" && companyCode.trim()) {
      whereConditions.push(`company_code = $${paramIndex}`);
      queryParams.push(companyCode.trim());
      paramIndex++;
      logger.info("회사 코드 필터 적용", { companyCode });
    }

    // 최고 관리자 필터링 (회사 관리자와 일반 사용자는 최고 관리자를 볼 수 없음)
    if (req.user && req.user.companyCode !== "*") {
      // 최고 관리자가 아닌 경우, company_code가 "*"인 사용자는 제외
      whereConditions.push(`company_code != '*'`);
      logger.info("최고 관리자 필터링 적용", {
        userCompanyCode: req.user.companyCode,
      });
    }

    // 검색 조건 처리
    if (search && typeof search === "string" && search.trim()) {
      // 통합 검색
      searchType = "v2";
      const searchTerm = search.trim();

      whereConditions.push(`(
        sabun ILIKE $${paramIndex} OR
        user_type_name ILIKE $${paramIndex} OR
        dept_name ILIKE $${paramIndex} OR
        position_name ILIKE $${paramIndex} OR
        user_id ILIKE $${paramIndex} OR
        user_name ILIKE $${paramIndex} OR
        tel ILIKE $${paramIndex} OR
        cell_phone ILIKE $${paramIndex} OR
        email ILIKE $${paramIndex}
      )`);
      queryParams.push(`%${searchTerm}%`);
      paramIndex++;

      logger.info("통합 검색 실행", { searchTerm });
    } else if (searchField && searchValue) {
      // 단일 필드 검색
      searchType = "single";
      const fieldMap: { [key: string]: string } = {
        sabun: "sabun",
        companyName: "user_type_name",
        deptName: "dept_name",
        positionName: "position_name",
        userId: "user_id",
        userName: "user_name",
        tel: "tel",
        cellPhone: "cell_phone",
        email: "email",
      };

      if (fieldMap[searchField as string]) {
        if (searchField === "tel") {
          whereConditions.push(
            `(tel ILIKE $${paramIndex} OR cell_phone ILIKE $${paramIndex})`
          );
          queryParams.push(`%${searchValue}%`);
          paramIndex++;
        } else {
          whereConditions.push(
            `${fieldMap[searchField as string]} ILIKE $${paramIndex}`
          );
          queryParams.push(`%${searchValue}%`);
          paramIndex++;
        }
        logger.info("단일 필드 검색 실행", { searchField, searchValue });
      }
    } else {
      // 고급 검색 (개별 필드별 AND 조건)
      const advancedSearchFields = [
        { param: search_sabun, field: "sabun" },
        { param: search_companyName, field: "user_type_name" },
        { param: search_deptName, field: "dept_name" },
        { param: search_positionName, field: "position_name" },
        { param: search_userId, field: "user_id" },
        { param: search_userName, field: "user_name" },
        { param: search_email, field: "email" },
      ];

      let hasAdvancedSearch = false;

      for (const { param, field } of advancedSearchFields) {
        if (param && typeof param === "string" && param.trim()) {
          whereConditions.push(`${field} ILIKE $${paramIndex}`);
          queryParams.push(`%${param.trim()}%`);
          paramIndex++;
          hasAdvancedSearch = true;
        }
      }

      // 전화번호 검색
      if (search_tel && typeof search_tel === "string" && search_tel.trim()) {
        whereConditions.push(
          `(tel ILIKE $${paramIndex} OR cell_phone ILIKE $${paramIndex})`
        );
        queryParams.push(`%${search_tel.trim()}%`);
        paramIndex++;
        hasAdvancedSearch = true;
      }

      if (hasAdvancedSearch) {
        searchType = "advanced";
        logger.info("고급 검색 실행", {
          search_sabun,
          search_companyName,
          search_deptName,
          search_positionName,
          search_userId,
          search_userName,
          search_tel,
          search_email,
        });
      }
    }

    // 현재 로그인한 사용자의 회사 코드 필터 (슈퍼관리자가 아닌 경우)
    if (req.user && req.user.companyCode !== "*" && !companyCode) {
      whereConditions.push(`company_code = $${paramIndex}`);
      queryParams.push(req.user.companyCode);
      paramIndex++;
      logger.info("사용자 회사 코드 필터 적용", {
        companyCode: req.user.companyCode,
      });
    }

    // 기존 필터들
    if (deptCode) {
      whereConditions.push(`dept_code = $${paramIndex}`);
      queryParams.push(deptCode);
      paramIndex++;
    }

    if (status) {
      whereConditions.push(`status = $${paramIndex}`);
      queryParams.push(status);
      paramIndex++;
    }

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

    // 총 개수 조회
    const countQuery = `
      SELECT COUNT(*) as total
      FROM user_info
      ${whereClause}
    `;
    const countResult = await query<{ total: string }>(countQuery, queryParams);
    const totalCount = parseInt(countResult[0]?.total || "0", 10);

    // 사용자 목록 조회
    const limit = size ? Number(size) : Number(countPerPage);
    const offset = (Number(page) - 1) * limit;
    const usersQuery = `
      SELECT
        sabun,
        user_id,
        user_name,
        user_name_eng,
        dept_code,
        dept_name,
        position_code,
        position_name,
        email,
        tel,
        cell_phone,
        user_type,
        user_type_name,
        regdate,
        status,
        company_code,
        locale
      FROM user_info
      ${whereClause}
      ORDER BY regdate DESC, user_name ASC
      LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
    `;

    const users = await query<any>(usersQuery, [...queryParams, limit, offset]);

    // 응답 데이터 가공
    const processedUsers = users.map((user) => ({
      userId: user.user_id,
      userName: user.user_name,
      userNameEng: user.user_name_eng || null,
      sabun: user.sabun || null,
      deptCode: user.dept_code || null,
      deptName: user.dept_name || null,
      positionCode: user.position_code || null,
      positionName: user.position_name || null,
      email: user.email || null,
      tel: user.tel || null,
      cellPhone: user.cell_phone || null,
      userType: user.user_type || null,
      userTypeName: user.user_type_name || null,
      status: user.status || "active",
      companyCode: user.company_code || null,
      locale: user.locale || null,
      regDate: user.regdate
        ? new Date(user.regdate).toISOString().split("T")[0]
        : null,
    }));

    const response = {
      success: true,
      data: processedUsers,
      total: totalCount,
      searchType,
      pagination: {
        page: Number(page),
        limit: limit,
        totalPages: Math.ceil(totalCount / limit),
      },
      message: "사용자 목록 조회 성공",
    };

    logger.info("사용자 목록 조회 성공", {
      totalCount,
      returnedCount: processedUsers.length,
      searchType,
      currentPage: Number(page),
      limit: limit,
      companyCode: companyCode || "all",
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 목록 조회 실패", { error });
    res.status(500).json({
      success: false,
      message: "사용자 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * GET /api/admin/user-locale
 * 사용자 로케일 조회 API
 */
export const getUserLocale = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    logger.debug("사용자 로케일 조회 요청", {
      query: req.query,
      user: req.user,
    });

    if (!req.user?.userId) {
      res.status(400).json({
        success: false,
        message: "사용자 정보가 없습니다.",
      });
      return;
    }

    // Raw Query로 사용자 로케일 조회
    const userInfo = await queryOne<{ locale: string }>(
      "SELECT locale FROM user_info WHERE user_id = $1",
      [req.user.userId]
    );

    let userLocale = "en"; // 기본값

    if (userInfo?.locale) {
      userLocale = userInfo.locale;
      logger.debug("데이터베이스에서 사용자 로케일 조회 성공", {
        userId: req.user.userId,
        locale: userLocale,
      });
    } else {
      logger.info("사용자 로케일이 설정되지 않음, 기본값 사용", {
        userId: req.user.userId,
        defaultLocale: userLocale,
      });
    }

    const response = {
      success: true,
      data: userLocale,
      message: "사용자 로케일 조회 성공",
    };

    logger.debug("사용자 로케일 조회 성공", {
      userLocale,
      userId: req.user.userId,
      fromDatabase: !!userInfo?.locale,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 로케일 조회 실패", { error });
    res.status(500).json({
      success: false,
      message: "사용자 로케일 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * 사용자 로케일 설정
 */
export const setUserLocale = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    logger.info("사용자 로케일 설정 요청", {
      body: req.body,
      user: req.user,
    });

    if (!req.user?.userId) {
      res.status(400).json({
        success: false,
        message: "사용자 정보가 없습니다.",
      });
      return;
    }

    const { locale } = req.body;

    if (!locale) {
      res.status(400).json({
        success: false,
        message: "로케일이 필요합니다.",
      });
      return;
    }

    // language_master 테이블에서 유효한 언어 코드인지 확인
    const validLang = await queryOne<{ lang_code: string }>(
      "SELECT lang_code FROM language_master WHERE lang_code = $1 AND is_active = 'Y'",
      [locale]
    );

    if (!validLang) {
      res.status(400).json({
        success: false,
        message: `유효하지 않은 로케일입니다: ${locale}`,
      });
      return;
    }

    // Raw Query로 사용자 로케일 저장
    await query("UPDATE user_info SET locale = $1 WHERE user_id = $2", [
      locale,
      req.user.userId,
    ]);

    logger.info("사용자 로케일을 데이터베이스에 저장 완료", {
      locale,
      userId: req.user.userId,
    });

    const response = {
      success: true,
      data: locale,
      message: "사용자 로케일 설정 성공",
    };

    logger.info("사용자 로케일 설정 성공", {
      locale,
      userId: req.user.userId,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 로케일 설정 실패", { error });
    res.status(500).json({
      success: false,
      message: "사용자 로케일 설정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * GET /api/admin/companies
 * 회사 목록 조회 API
 * 기존 Java AdminController의 회사 목록 조회 기능 포팅
 */
export const getCompanyList = async (
  req: AuthenticatedRequest,
  res: Response
) => {
  try {
    logger.debug("회사 목록 조회 요청", {
      query: req.query,
      user: req.user,
    });

    // Raw Query로 회사 목록 조회
    const companies = await query<any>(
      `      SELECT 
        company_code, 
        company_name,
        business_registration_number,
        representative_name,
        representative_phone,
        email,
        website,
        address,
        status,
        writer,
        regdate
      FROM company_mng
      WHERE status = 'active' OR status IS NULL
      ORDER BY company_name ASC`
    );

    // 프론트엔드에서 기대하는 응답 형식으로 변환
    const response = {
      success: true,
      data: companies.map((company) => ({
        company_code: company.company_code,
        company_name: company.company_name,
        status: company.status || "active",
        writer: company.writer,
        regdate: company.regdate
          ? new Date(company.regdate).toISOString()
          : new Date().toISOString(),
        data_type: "company",
      })),
      message: "회사 목록 조회 성공",
    };

    logger.debug("회사 목록 조회 성공", {
      totalCount: companies.length,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("회사 목록 조회 실패", { error });
    res.status(500).json({
      success: false,
      message: "회사 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * 다국어 언어 목록 조회 (더미 데이터)
 */
export async function getLanguageList(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    logger.info("다국어 언어 목록 조회 요청");

    // 더미 데이터 반환
    const languages = [
      {
        langCode: "KR",
        langName: "한국어",
        langNative: "한국어",
        isActive: "Y",
      },
      {
        langCode: "EN",
        langName: "English",
        langNative: "English",
        isActive: "Y",
      },
      {
        langCode: "JP",
        langName: "日本語",
        langNative: "日本語",
        isActive: "Y",
      },
      { langCode: "CN", langName: "中文", langNative: "中文", isActive: "Y" },
    ];

    const response: ApiResponse<any[]> = {
      success: true,
      message: "언어 목록 조회 성공",
      data: languages,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("언어 목록 조회 실패:", error);
    res.status(500).json({
      success: false,
      message: "언어 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 키 목록 조회
 */
export async function getLangKeyList(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    logger.info("다국어 키 목록 조회 요청", {
      query: req.query,
      user: req.user,
    });

    // Raw Query로 다국어 키 목록 조회
    const result = await query<any>(
      `SELECT
        key_id,
        company_code,
        menu_name,
        lang_key,
        description,
        is_active,
        created_date,
        created_by,
        updated_date,
        updated_by
      FROM multi_lang_key_master
      ORDER BY company_code ASC, menu_name ASC, lang_key ASC`
    );

    const langKeys = result.map((row) => ({
      keyId: row.key_id,
      companyCode: row.company_code,
      menuName: row.menu_name,
      langKey: row.lang_key,
      description: row.description,
      isActive: row.is_active,
      createdDate: row.created_date
        ? new Date(row.created_date).toISOString()
        : null,
      createdBy: row.created_by,
      updatedDate: row.updated_date
        ? new Date(row.updated_date).toISOString()
        : null,
      updatedBy: row.updated_by,
    }));

    // 프론트엔드에서 기대하는 응답 형식으로 변환
    const response: ApiResponse<any[]> = {
      success: true,
      message: "다국어 키 목록 조회 성공",
      data: langKeys,
    };

    logger.info("다국어 키 목록 조회 성공", {
      totalCount: langKeys.length,
      response: response,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 키 목록 조회 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 키 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 텍스트 목록 조회 (더미 데이터)
 */
export async function getLangTextList(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { keyId } = req.params;
    logger.info(`다국어 텍스트 목록 조회 요청: keyId = ${keyId}`);

    // 더미 데이터 반환
    const langTexts = [
      {
        textId: 1,
        keyId: parseInt(keyId),
        langCode: "KR",
        langText: "사용자 관리",
        isActive: "Y",
      },
      {
        textId: 2,
        keyId: parseInt(keyId),
        langCode: "EN",
        langText: "User Management",
        isActive: "Y",
      },
      {
        textId: 3,
        keyId: parseInt(keyId),
        langCode: "JP",
        langText: "ユーザー管理",
        isActive: "Y",
      },
    ];

    const response: ApiResponse<any[]> = {
      success: true,
      message: "다국어 텍스트 목록 조회 성공",
      data: langTexts,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 텍스트 목록 조회 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 텍스트 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 텍스트 저장 (더미 데이터)
 */
export async function saveLangTexts(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { keyId } = req.params;
    const textData = req.body;
    logger.info(`다국어 텍스트 저장 요청: keyId = ${keyId}`, { textData });

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "다국어 텍스트 저장 성공",
      data: { savedCount: textData.length },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 텍스트 저장 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 텍스트 저장 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 키 저장 (더미 데이터)
 */
export async function saveLangKey(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const keyData = req.body;
    logger.info("다국어 키 저장 요청", { keyData });

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "다국어 키 저장 성공",
      data: { keyId: Math.floor(Math.random() * 1000) + 1 },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 키 저장 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 키 저장 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 키 수정 (더미 데이터)
 */
export async function updateLangKey(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { keyId } = req.params;
    const keyData = req.body;
    logger.info(`다국어 키 수정 요청: keyId = ${keyId}`, { keyData });

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "다국어 키 수정 성공",
      data: { keyId: parseInt(keyId) },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 키 수정 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 키 수정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 키 삭제 (더미 데이터)
 */
export async function deleteLangKey(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { keyId } = req.params;
    logger.info(`다국어 키 삭제 요청: keyId = ${keyId}`);

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "다국어 키 삭제 성공",
      data: { deletedKeyId: parseInt(keyId) },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 키 삭제 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 키 삭제 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 다국어 키 상태 토글 (더미 데이터)
 */
export async function toggleLangKeyStatus(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { keyId } = req.params;
    logger.info(`다국어 키 상태 토글 요청: keyId = ${keyId}`);

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "다국어 키 상태 토글 성공",
      data: "활성화",
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("다국어 키 상태 토글 실패:", error);
    res.status(500).json({
      success: false,
      message: "다국어 키 상태 토글 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 언어 저장 (더미 데이터)
 */
export async function saveLanguage(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const langData = req.body;
    logger.info("언어 저장 요청", { langData });

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "언어 저장 성공",
      data: { langCode: langData.langCode },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("언어 저장 실패:", error);
    res.status(500).json({
      success: false,
      message: "언어 저장 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 언어 수정 (더미 데이터)
 */
export async function updateLanguage(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { langCode } = req.params;
    const langData = req.body;
    logger.info(`언어 수정 요청: langCode = ${langCode}`, { langData });

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "언어 수정 성공",
      data: { langCode },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("언어 수정 실패:", error);
    res.status(500).json({
      success: false,
      message: "언어 수정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 언어 상태 토글 (더미 데이터)
 */
export async function toggleLanguageStatus(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { langCode } = req.params;
    logger.info(`언어 상태 토글 요청: langCode = ${langCode}`);

    // 더미 응답
    const response: ApiResponse<any> = {
      success: true,
      message: "언어 상태 토글 성공",
      data: "활성화",
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("언어 상태 토글 실패:", error);
    res.status(500).json({
      success: false,
      message: "언어 상태 토글 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 메뉴 저장 (추가/수정)
 */
export async function saveMenu(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const menuData = req.body;
    logger.info("메뉴 저장 요청", { menuData, user: req.user });

    // 사용자의 company_code 확인
    if (!req.user?.companyCode) {
      res.status(400).json({
        success: false,
        message: "사용자의 회사 코드를 찾을 수 없습니다.",
        error: "Missing company_code",
      });
      return;
    }

    const userCompanyCode = req.user.companyCode;
    const userType = req.user.userType;
    let requestCompanyCode = menuData.companyCode || menuData.company_code;

    // "none"이나 빈 값은 undefined로 처리하여 사용자 회사 코드 사용
    if (
      requestCompanyCode === "none" ||
      requestCompanyCode === "" ||
      !requestCompanyCode
    ) {
      requestCompanyCode = undefined;
    }

    // 공통 메뉴(company_code = '*')는 최고 관리자만 생성 가능
    if (requestCompanyCode === "*") {
      if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
        res.status(403).json({
          success: false,
          message: "공통 메뉴는 최고 관리자만 생성할 수 있습니다.",
          error: "Unauthorized to create common menu",
        });
        return;
      }
    } else if (userCompanyCode !== "*") {
      // 회사 관리자는 자기 회사 메뉴만 생성 가능
      // requestCompanyCode가 undefined면 사용자 회사 코드 사용 (권한 체크 통과)
      if (requestCompanyCode && requestCompanyCode !== userCompanyCode) {
        res.status(403).json({
          success: false,
          message: "해당 회사의 메뉴를 생성할 권한이 없습니다.",
          error: "Unauthorized to create menu for this company",
        });
        return;
      }
    }

    // Raw Query를 사용한 메뉴 저장
    const objid = Date.now(); // 고유 ID 생성
    const companyCode = requestCompanyCode || userCompanyCode;

    // menu_url이 비어있으면 screen_code도 null로 설정
    const menuUrl = menuData.menuUrl || null;
    const screenCode = menuUrl ? menuData.screenCode || null : null;

    const [savedMenu] = await query<any>(
      `INSERT INTO menu_info (
        objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng,
        seq, menu_url, menu_desc, writer, regdate, status,
        system_name, company_code, lang_key, lang_key_desc, screen_code, menu_icon
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
      RETURNING *`,
      [
        objid,
        menuData.menuType ? Number(menuData.menuType) : null,
        menuData.parentObjId ? Number(menuData.parentObjId) : null,
        menuData.menuNameKor,
        menuData.menuNameEng || null,
        menuData.seq ? Number(menuData.seq) : null,
        menuUrl,
        menuData.menuDesc || null,
        req.user?.userId || "admin",
        new Date(),
        menuData.status || "active",
        menuData.systemName || null,
        companyCode,
        menuData.langKey || null,
        menuData.langKeyDesc || null,
        screenCode,
        menuData.menuIcon || null,
      ]
    );

    logger.info("메뉴 저장 성공", { savedMenu });

    // 다국어 메뉴 카테고리 자동 생성
    try {
      const { MultiLangService } = await import("../services/multilangService");
      const multilangService = new MultiLangService();
      
      // 회사명 조회
      const companyInfo = await queryOne<{ company_name: string }>(
        `SELECT company_name FROM company_mng WHERE company_code = $1`,
        [companyCode]
      );
      const companyName = companyCode === "*" ? "공통" : (companyInfo?.company_name || companyCode);
      
      // 메뉴 경로 조회 및 카테고리 생성
      const menuPath = await multilangService.getMenuPath(savedMenu.objid.toString());
      await multilangService.ensureMenuCategory(companyCode, companyName, menuPath);
      
      logger.info("메뉴 다국어 카테고리 생성 완료", {
        menuObjId: savedMenu.objid.toString(),
        menuPath,
      });
    } catch (categoryError) {
      logger.warn("메뉴 다국어 카테고리 생성 실패 (메뉴 저장은 성공)", {
        menuObjId: savedMenu.objid.toString(),
        error: categoryError,
      });
    }

    const response: ApiResponse<any> = {
      success: true,
      message: "메뉴가 성공적으로 저장되었습니다.",
      data: {
        objid: savedMenu.objid.toString(),
        menuNameKor: savedMenu.menu_name_kor,
        menuNameEng: savedMenu.menu_name_eng,
        menuUrl: savedMenu.menu_url,
        menuDesc: savedMenu.menu_desc,
        status: savedMenu.status,
        writer: savedMenu.writer,
        regdate: new Date(savedMenu.regdate).toISOString(),
      },
    };

    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "CREATE",
      resourceType: "MENU",
      resourceId: savedMenu.objid?.toString(),
      resourceName: savedMenu.menu_name_kor,
      summary: `메뉴 "${savedMenu.menu_name_kor}" 생성`,
      changes: { after: { menuNameKor: savedMenu.menu_name_kor, menuUrl: savedMenu.menu_url, status: savedMenu.status } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("메뉴 저장 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 저장 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 메뉴 수정
 */
export async function updateMenu(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { menuId } = req.params;
    const menuData = req.body;
    logger.info(`메뉴 수정 요청: menuId = ${menuId}`, {
      menuData,
      user: req.user,
    });

    // 사용자의 company_code 확인
    if (!req.user?.companyCode) {
      res.status(400).json({
        success: false,
        message: "사용자의 회사 코드를 찾을 수 없습니다.",
        error: "Missing company_code",
      });
      return;
    }

    const userCompanyCode = req.user.companyCode;
    const userType = req.user.userType;

    // 수정하려는 메뉴 조회
    const currentMenu = await queryOne<any>(
      `SELECT objid, company_code FROM menu_info WHERE objid = $1`,
      [Number(menuId)]
    );

    if (!currentMenu) {
      res.status(404).json({
        success: false,
        message: `메뉴를 찾을 수 없습니다: ${menuId}`,
        error: "Menu not found",
      });
      return;
    }

    // 공통 메뉴(company_code = '*')는 최고 관리자만 수정 가능
    if (currentMenu.company_code === "*") {
      if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
        res.status(403).json({
          success: false,
          message: "공통 메뉴는 최고 관리자만 수정할 수 있습니다.",
          error: "Unauthorized to update common menu",
        });
        return;
      }
    } else if (userCompanyCode !== "*") {
      // 회사 관리자는 자기 회사 메뉴만 수정 가능
      if (currentMenu.company_code !== userCompanyCode) {
        res.status(403).json({
          success: false,
          message: "해당 회사의 메뉴를 수정할 권한이 없습니다.",
          error: "Unauthorized to update menu for this company",
        });
        return;
      }
    }

    let requestCompanyCode =
      menuData.companyCode || menuData.company_code;

    // "none"이나 빈 값은 기존 메뉴의 회사 코드 유지
    if (
      requestCompanyCode === "none" ||
      requestCompanyCode === "" ||
      !requestCompanyCode
    ) {
      requestCompanyCode = currentMenu.company_code;
    }

    // company_code 변경 시도하는 경우 권한 체크
    if (requestCompanyCode !== currentMenu.company_code) {
      // 공통 메뉴로 변경하려는 경우 최고 관리자만 가능
      if (requestCompanyCode === "*") {
        if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
          res.status(403).json({
            success: false,
            message: "공통 메뉴로 변경할 권한이 없습니다.",
            error: "Unauthorized to change to common menu",
          });
          return;
        }
      }
      // 회사 관리자는 자기 회사로만 변경 가능
      else if (
        userCompanyCode !== "*" &&
        requestCompanyCode !== userCompanyCode
      ) {
        res.status(403).json({
          success: false,
          message: "해당 회사로 변경할 권한이 없습니다.",
          error: "Unauthorized to change company",
        });
        return;
      }
    }

    const companyCode = requestCompanyCode;

    // menu_url이 비어있으면 screen_code도 null로 설정
    const menuUrl = menuData.menuUrl || null;
    const screenCode = menuUrl ? menuData.screenCode || null : null;

    // Raw Query를 사용한 메뉴 수정
    const [updatedMenu] = await query<any>(
      `UPDATE menu_info SET
        menu_type = $1,
        parent_obj_id = $2,
        menu_name_kor = $3,
        menu_name_eng = $4,
        seq = $5,
        menu_url = $6,
        menu_desc = $7,
        status = $8,
        system_name = $9,
        company_code = $10,
        lang_key = $11,
        lang_key_desc = $12,
        screen_code = $13,
        menu_icon = $14
      WHERE objid = $15
      RETURNING *`,
      [
        menuData.menuType ? Number(menuData.menuType) : null,
        menuData.parentObjId ? Number(menuData.parentObjId) : null,
        menuData.menuNameKor,
        menuData.menuNameEng || null,
        menuData.seq ? Number(menuData.seq) : null,
        menuUrl,
        menuData.menuDesc || null,
        menuData.status || "active",
        menuData.systemName || null,
        companyCode,
        menuData.langKey || null,
        menuData.langKeyDesc || null,
        screenCode,
        menuData.menuIcon || null,
        Number(menuId),
      ]
    );

    // menu_url이 비어있으면 화면 할당도 해제 (screen_menu_assignments의 is_active를 'N'으로)
    if (!menuUrl) {
      await query(
        `UPDATE screen_menu_assignments 
         SET is_active = 'N'
         WHERE menu_objid = $1 AND company_code = $2`,
        [Number(menuId), companyCode]
      );
      logger.info("화면 할당 비활성화", { menuId, companyCode });
    }

    logger.info("메뉴 수정 성공", { updatedMenu });

    const response: ApiResponse<any> = {
      success: true,
      message: "메뉴가 성공적으로 수정되었습니다.",
      data: {
        objid: updatedMenu.objid.toString(),
        menuNameKor: updatedMenu.menu_name_kor,
        menuNameEng: updatedMenu.menu_name_eng,
        menuUrl: updatedMenu.menu_url,
        menuDesc: updatedMenu.menu_desc,
        status: updatedMenu.status,
        writer: updatedMenu.writer,
        regdate: new Date(updatedMenu.regdate).toISOString(),
      },
    };

    auditLogService.log({
      companyCode: req.user?.companyCode || updatedMenu.company_code || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "UPDATE",
      resourceType: "MENU",
      resourceId: updatedMenu.objid?.toString(),
      resourceName: updatedMenu.menu_name_kor,
      summary: `메뉴 "${updatedMenu.menu_name_kor}" 수정`,
      changes: {
        before: { menuNameKor: currentMenu.menu_name_kor, menuUrl: currentMenu.menu_url, status: currentMenu.status },
        after: { menuNameKor: updatedMenu.menu_name_kor, menuUrl: updatedMenu.menu_url, status: updatedMenu.status },
      },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("메뉴 수정 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 수정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 재귀적으로 모든 하위 메뉴 ID를 수집하는 헬퍼 함수
 */
async function collectAllChildMenuIds(parentObjid: number): Promise<number[]> {
  const allIds: number[] = [];
  
  // 직접 자식 메뉴들 조회
  const children = await query<any>(
    `SELECT objid FROM menu_info WHERE parent_obj_id = $1`,
    [parentObjid]
  );
  
  for (const child of children) {
    allIds.push(child.objid);
    // 자식의 자식들도 재귀적으로 수집
    const grandChildren = await collectAllChildMenuIds(child.objid);
    allIds.push(...grandChildren);
  }
  
  return allIds;
}

/**
 * 메뉴 및 관련 데이터 정리 헬퍼 함수
 */
async function cleanupMenuRelatedData(menuObjid: number): Promise<void> {
  // 1. code_category에서 menu_objid를 NULL로 설정
  await query(
    `UPDATE code_category SET menu_objid = NULL WHERE menu_objid = $1`,
    [menuObjid]
  );
  
  // 3. code_info에서 menu_objid를 NULL로 설정
  await query(
    `UPDATE code_info SET menu_objid = NULL WHERE menu_objid = $1`,
    [menuObjid]
  );
  
  // 4. numbering_rules: 새 스키마에서는 메뉴와 연결되지 않음 (스킵)
  // 새 스키마: table_name + column_name + company_code 기반
  
  // 5. rel_menu_auth에서 관련 권한 삭제
  await query(
    `DELETE FROM rel_menu_auth WHERE menu_objid = $1`,
    [menuObjid]
  );
  
  // 6. screen_menu_assignments에서 관련 할당 삭제
  await query(
    `DELETE FROM screen_menu_assignments WHERE menu_objid = $1`,
    [menuObjid]
  );
  
  // 7. screen_groups에서 menu_objid를 NULL로 설정
  await query(
    `UPDATE screen_groups SET menu_objid = NULL WHERE menu_objid = $1`,
    [menuObjid]
  );
}

/**
 * 메뉴 삭제
 */
export async function deleteMenu(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { menuId } = req.params;
    logger.info(`메뉴 삭제 요청: menuId = ${menuId}`, { user: req.user });

    // 사용자의 company_code 확인
    if (!req.user?.companyCode) {
      res.status(400).json({
        success: false,
        message: "사용자의 회사 코드를 찾을 수 없습니다.",
        error: "Missing company_code",
      });
      return;
    }

    const userCompanyCode = req.user.companyCode;
    const userType = req.user.userType;

    // 삭제하려는 메뉴 조회
    const currentMenu = await queryOne<any>(
      `SELECT objid, company_code, menu_name_kor FROM menu_info WHERE objid = $1`,
      [Number(menuId)]
    );

    if (!currentMenu) {
      res.status(404).json({
        success: false,
        message: `메뉴를 찾을 수 없습니다: ${menuId}`,
        error: "Menu not found",
      });
      return;
    }

    // 공통 메뉴(company_code = '*')는 최고 관리자만 삭제 가능
    if (currentMenu.company_code === "*") {
      if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
        res.status(403).json({
          success: false,
          message: "공통 메뉴는 최고 관리자만 삭제할 수 있습니다.",
          error: "Unauthorized to delete common menu",
        });
        return;
      }
    } else if (userCompanyCode !== "*") {
      // 회사 관리자는 자기 회사 메뉴만 삭제 가능
      if (currentMenu.company_code !== userCompanyCode) {
        res.status(403).json({
          success: false,
          message: "해당 회사의 메뉴를 삭제할 권한이 없습니다.",
          error: "Unauthorized to delete menu for this company",
        });
        return;
      }
    }

    const menuObjid = Number(menuId);
    
    // 하위 메뉴들 재귀적으로 수집
    const childMenuIds = await collectAllChildMenuIds(menuObjid);
    const allMenuIdsToDelete = [menuObjid, ...childMenuIds];
    
    logger.info(`메뉴 삭제 대상: 본인(${menuObjid}) + 하위 메뉴 ${childMenuIds.length}개`, {
      menuName: currentMenu.menu_name_kor,
      totalCount: allMenuIdsToDelete.length,
      childMenuIds,
    });

    // 모든 삭제 대상 메뉴에 대해 관련 데이터 정리
    for (const objid of allMenuIdsToDelete) {
      await cleanupMenuRelatedData(objid);
    }

    logger.info("메뉴 관련 데이터 정리 완료", { 
      menuObjid, 
      totalCleaned: allMenuIdsToDelete.length 
    });

    // 하위 메뉴부터 역순으로 삭제 (외래키 제약 회피)
    // 가장 깊은 하위부터 삭제해야 하므로 역순으로
    const reversedIds = [...allMenuIdsToDelete].reverse();
    
    for (const objid of reversedIds) {
      await query(`DELETE FROM menu_info WHERE objid = $1`, [objid]);
    }

    logger.info("메뉴 삭제 성공", { 
      deletedMenuObjid: menuObjid,
      deletedMenuName: currentMenu.menu_name_kor,
      totalDeleted: allMenuIdsToDelete.length,
    });

    const response: ApiResponse<any> = {
      success: true,
      message: `메뉴가 성공적으로 삭제되었습니다. (하위 메뉴 ${childMenuIds.length}개 포함)`,
      data: {
        objid: menuObjid.toString(),
        menuNameKor: currentMenu.menu_name_kor,
        deletedCount: allMenuIdsToDelete.length,
        deletedChildCount: childMenuIds.length,
      },
    };

    auditLogService.log({
      companyCode: currentMenu.company_code || req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "DELETE",
      resourceType: "MENU",
      resourceId: menuObjid.toString(),
      resourceName: currentMenu.menu_name_kor,
      summary: `메뉴 "${currentMenu.menu_name_kor}" 삭제 (하위 ${childMenuIds.length}개 포함, 총 ${allMenuIdsToDelete.length}건)`,
      changes: { before: { menuNameKor: currentMenu.menu_name_kor } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("메뉴 삭제 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 삭제 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 메뉴 일괄 삭제
 */
export async function deleteMenusBatch(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const menuIds = req.body as string[];
    logger.info("메뉴 일괄 삭제 요청", { menuIds, user: req.user });

    if (!Array.isArray(menuIds) || menuIds.length === 0) {
      res.status(400).json({
        success: false,
        message: "삭제할 메뉴 ID 목록이 필요합니다.",
      });
      return;
    }

    // 사용자의 company_code 확인
    if (!req.user?.companyCode) {
      res.status(400).json({
        success: false,
        message: "사용자의 회사 코드를 찾을 수 없습니다.",
        error: "Missing company_code",
      });
      return;
    }

    const userCompanyCode = req.user.companyCode;
    const userType = req.user.userType;

    // 삭제하려는 메뉴들의 company_code 확인
    const menusToDelete = await query<any>(
      `SELECT objid, company_code FROM menu_info WHERE objid = ANY($1::bigint[])`,
      [menuIds.map((id) => Number(id))]
    );

    // 권한 체크: 공통 메뉴 포함 여부 확인
    const hasCommonMenu = menusToDelete.some(
      (menu: any) => menu.company_code === "*"
    );
    if (
      hasCommonMenu &&
      (userCompanyCode !== "*" || userType !== "SUPER_ADMIN")
    ) {
      res.status(403).json({
        success: false,
        message: "공통 메뉴는 최고 관리자만 삭제할 수 있습니다.",
        error: "Unauthorized to delete common menu",
      });
      return;
    }

    // 회사 관리자는 자기 회사 메뉴만 삭제 가능
    if (userCompanyCode !== "*") {
      const unauthorizedMenus = menusToDelete.filter(
        (menu: any) =>
          menu.company_code !== userCompanyCode && menu.company_code !== "*"
      );
      if (unauthorizedMenus.length > 0) {
        res.status(403).json({
          success: false,
          message: "다른 회사의 메뉴를 삭제할 권한이 없습니다.",
          error: "Unauthorized to delete menus for other companies",
        });
        return;
      }
    }

    // 모든 삭제 대상 메뉴 ID 수집 (하위 메뉴 포함)
    const allMenuIdsToDelete = new Set<number>();
    
    for (const menuId of menuIds) {
      const objid = Number(menuId);
      allMenuIdsToDelete.add(objid);
      
      // 하위 메뉴들 재귀적으로 수집
      const childMenuIds = await collectAllChildMenuIds(objid);
      childMenuIds.forEach(id => allMenuIdsToDelete.add(Number(id)));
    }
    
    const allIdsArray = Array.from(allMenuIdsToDelete);
    
    logger.info(`메뉴 일괄 삭제 대상: 선택 ${menuIds.length}개 + 하위 메뉴 포함 총 ${allIdsArray.length}개`, {
      selectedMenuIds: menuIds,
      totalWithChildren: allIdsArray.length,
    });

    // 모든 삭제 대상 메뉴에 대해 관련 데이터 정리
    for (const objid of allIdsArray) {
      await cleanupMenuRelatedData(objid);
    }

    logger.info("메뉴 관련 데이터 정리 완료", { 
      totalCleaned: allIdsArray.length 
    });

    // Raw Query를 사용한 메뉴 일괄 삭제
    let deletedCount = 0;
    let failedCount = 0;
    const deletedMenus: any[] = [];
    const failedMenuIds: string[] = [];

    // 하위 메뉴부터 삭제하기 위해 역순으로 정렬
    const reversedIds = [...allIdsArray].reverse();

    // 각 메뉴 ID에 대해 삭제 시도
    for (const menuObjid of reversedIds) {
      try {
        const result = await query<any>(
          `DELETE FROM menu_info WHERE objid = $1 RETURNING *`,
          [menuObjid]
        );

        if (result.length > 0) {
          deletedCount++;
          deletedMenus.push({
            ...result[0],
            objid: result[0].objid.toString(),
          });
        } else {
          failedCount++;
          failedMenuIds.push(String(menuObjid));
        }
      } catch (error) {
        logger.error(`메뉴 삭제 실패 (ID: ${menuObjid}):`, error);
        failedCount++;
        failedMenuIds.push(String(menuObjid));
      }
    }

    logger.info("메뉴 일괄 삭제 완료", {
      requested: menuIds.length,
      totalWithChildren: allIdsArray.length,
      deletedCount,
      failedCount,
      failedMenuIds,
    });

    const response: ApiResponse<any> = {
      success: true,
      message: `메뉴 일괄 삭제 완료: ${deletedCount}개 삭제, ${failedCount}개 실패`,
      data: {
        deletedCount,
        failedCount,
        total: menuIds.length,
        deletedMenus,
        failedMenuIds,
      },
    };

    if (deletedCount > 0) {
      auditLogService.log({
        companyCode: req.user?.companyCode || "",
        userId: req.user?.userId || "",
        userName: req.user?.userName || "",
        action: "DELETE",
        resourceType: "MENU",
        summary: `메뉴 일괄 삭제: ${deletedCount}개 삭제, ${failedCount}개 실패`,
        changes: { before: { deletedMenus, failedMenuIds } },
        ipAddress: getClientIp(req),
        requestPath: req.originalUrl,
      });
    }

    res.status(200).json(response);
  } catch (error) {
    logger.error("메뉴 일괄 삭제 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 일괄 삭제 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 메뉴 활성/비활성 토글
 */
export async function toggleMenuStatus(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { menuId } = req.params;
    logger.info(`메뉴 상태 토글 요청: menuId = ${menuId}`, { user: req.user });

    // 사용자의 company_code 확인
    if (!req.user?.companyCode) {
      res.status(400).json({
        success: false,
        message: "사용자의 회사 코드를 찾을 수 없습니다.",
        error: "Missing company_code",
      });
      return;
    }

    const userCompanyCode = req.user.companyCode;
    const userType = req.user.userType;

    // 현재 상태 및 회사 코드 조회
    const currentMenu = await queryOne<any>(
      `SELECT objid, status, company_code, menu_name_kor FROM menu_info WHERE objid = $1`,
      [Number(menuId)]
    );

    if (!currentMenu) {
      res.status(404).json({
        success: false,
        message: `메뉴를 찾을 수 없습니다: ${menuId}`,
        error: "Menu not found",
      });
      return;
    }

    // 공통 메뉴(company_code = '*')는 최고 관리자만 상태 변경 가능
    if (currentMenu.company_code === "*") {
      if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
        res.status(403).json({
          success: false,
          message: "공통 메뉴는 최고 관리자만 상태를 변경할 수 있습니다.",
          error: "Unauthorized to toggle common menu status",
        });
        return;
      }
    } else if (userCompanyCode !== "*") {
      // 회사 관리자는 자기 회사 메뉴만 상태 변경 가능
      if (currentMenu.company_code !== userCompanyCode) {
        res.status(403).json({
          success: false,
          message: "해당 회사의 메뉴 상태를 변경할 권한이 없습니다.",
          error: "Unauthorized to toggle menu status for this company",
        });
        return;
      }
    }

    // 상태 토글 (active <-> inactive)
    const currentStatus = currentMenu.status;
    const newStatus = currentStatus === "active" ? "inactive" : "active";

    // 상태 업데이트
    const [updatedMenu] = await query<any>(
      `UPDATE menu_info SET status = $1 WHERE objid = $2 RETURNING *`,
      [newStatus, Number(menuId)]
    );

    logger.info("메뉴 상태 토글 성공", {
      menuId,
      oldStatus: currentStatus,
      newStatus,
    });

    const result = newStatus === "active" ? "활성화" : "비활성화";

    const response: ApiResponse<string> = {
      success: true,
      message: `메뉴가 ${result}되었습니다.`,
      data: result,
    };

    auditLogService.log({
      companyCode: currentMenu.company_code || req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "STATUS_CHANGE",
      resourceType: "MENU",
      resourceId: String(menuId),
      resourceName: currentMenu.menu_name_kor,
      summary: `메뉴 "${currentMenu.menu_name_kor}" 상태 변경: ${currentStatus} → ${newStatus}`,
      changes: { before: { status: currentStatus }, after: { status: newStatus }, fields: ["status"] },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("메뉴 상태 토글 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 상태 변경에 실패하였습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
      errorCode: "MENU_TOGGLE_ERROR",
    });
  }
}

/**
 * 회사 목록 조회 (실제 데이터베이스에서)
 */
export async function getCompanyListFromDB(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    logger.debug("회사 목록 조회 요청 (Raw Query)", { user: req.user });

    // Raw Query로 회사 목록 조회
    const companies = await query<any>(
      `      SELECT 
        company_code, 
        company_name,
        business_registration_number,
        representative_name,
        representative_phone,
        email,
        website,
        address,
        writer,
        regdate,
        status
      FROM company_mng
      ORDER BY regdate DESC`
    );

    logger.debug("회사 목록 조회 성공 (Raw Query)", { count: companies.length });

    const response: ApiResponse<any> = {
      success: true,
      message: "회사 목록 조회 성공",
      data: companies,
      total: companies.length,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("회사 목록 조회 실패 (Raw Query):", error);
    res.status(500).json({
      success: false,
      message: "회사 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * GET /api/admin/departments
 * 부서 목록 조회 API
 * 기존 Java AdminController의 부서 목록 조회 기능 포팅
 */
export const getDepartmentList = async (
  req: AuthenticatedRequest,
  res: Response
) => {
  try {
    logger.info("부서 목록 조회 요청", {
      query: req.query,
      user: req.user,
    });

    const { companyCode, status, search } = req.query;

    // Raw Query를 사용한 부서 목록 조회
    let whereConditions: string[] = [];
    let queryParams: any[] = [];
    let paramIndex = 1;

    // 회사 코드 필터
    if (companyCode) {
      whereConditions.push(`company_code = $${paramIndex}`);
      queryParams.push(companyCode);
      paramIndex++;
    }

    // 상태 필터
    if (status) {
      whereConditions.push(`status = $${paramIndex}`);
      queryParams.push(status);
      paramIndex++;
    }

    // 검색 조건
    if (search && typeof search === "string" && search.trim()) {
      whereConditions.push(`(
        dept_name ILIKE $${paramIndex} OR
        dept_code ILIKE $${paramIndex} OR
        location_name ILIKE $${paramIndex}
      )`);
      queryParams.push(`%${search.trim()}%`);
      paramIndex++;
    }

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

    const departments = await query<any>(
      `SELECT
        dept_code,
        parent_dept_code,
        dept_name,
        master_sabun,
        master_user_id,
        location,
        location_name,
        regdate,
        data_type,
        status,
        sales_yn,
        company_code,
        company_name
      FROM dept_info
      ${whereClause}
      ORDER BY parent_dept_code ASC NULLS FIRST, dept_name ASC`,
      queryParams
    );

    // 부서 트리 구조 생성
    const deptMap = new Map();
    const rootDepartments: any[] = [];

    // 모든 부서를 맵에 저장
    departments.forEach((dept) => {
      deptMap.set(dept.dept_code, {
        deptCode: dept.dept_code,
        deptName: dept.dept_name,
        parentDeptCode: dept.parent_dept_code,
        masterSabun: dept.master_sabun,
        masterUserId: dept.master_user_id,
        location: dept.location,
        locationName: dept.location_name,
        regdate: dept.regdate ? new Date(dept.regdate).toISOString() : null,
        dataType: dept.data_type,
        status: dept.status || "active",
        salesYn: dept.sales_yn,
        companyCode: dept.company_code,
        companyName: dept.company_name,
        children: [],
      });
    });

    // 부서 트리 구조 생성
    departments.forEach((dept) => {
      const deptNode = deptMap.get(dept.dept_code);

      if (dept.parent_dept_code && deptMap.has(dept.parent_dept_code)) {
        // 상위 부서가 있으면 children에 추가
        const parentDept = deptMap.get(dept.parent_dept_code);
        parentDept.children.push(deptNode);
      } else {
        // 상위 부서가 없으면 루트 부서로 추가
        rootDepartments.push(deptNode);
      }
    });

    const response = {
      success: true,
      data: {
        departments: rootDepartments,
        flatList: departments.map((dept) => ({
          deptCode: dept.dept_code,
          deptName: dept.dept_name,
          parentDeptCode: dept.parent_dept_code,
          masterSabun: dept.master_sabun,
          masterUserId: dept.master_user_id,
          location: dept.location,
          locationName: dept.location_name,
          regdate: dept.regdate ? new Date(dept.regdate).toISOString() : null,
          dataType: dept.data_type,
          status: dept.status || "active",
          salesYn: dept.sales_yn,
          companyCode: dept.company_code,
          companyName: dept.company_name,
        })),
      },
      message: "부서 목록 조회 성공",
      total: departments.length,
    };

    logger.info("부서 목록 조회 성공", {
      totalCount: departments.length,
      rootCount: rootDepartments.length,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("부서 목록 조회 실패", { error });
    res.status(500).json({
      success: false,
      message: "부서 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * GET /api/admin/users/:userId
 * 사용자 상세 조회 API
 * 기존 Java AdminController의 사용자 상세 조회 기능 포팅
 */
export const getUserInfo = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { userId } = req.params;
    logger.info(`사용자 상세 조회 요청 - userId: ${userId}`, {
      user: req.user,
    });

    if (!userId) {
      res.status(400).json({
        success: false,
        message: "사용자 ID가 필요합니다.",
        error: {
          code: "USER_ID_REQUIRED",
          details: "userId parameter is required",
        },
      });
      return;
    }

    // Raw Query를 사용한 사용자 상세 정보 조회
    const user = await queryOne<any>(
      `SELECT * FROM user_info WHERE user_id = $1`,
      [userId]
    );

    if (!user) {
      res.status(404).json({
        success: false,
        message: "사용자를 찾을 수 없습니다.",
        error: {
          code: "USER_NOT_FOUND",
          details: `User ID: ${userId}`,
        },
      });
      return;
    }

    // 부서 정보 별도 조회
    const deptInfo = user.dept_code
      ? await queryOne<any>(
          `SELECT
            dept_name,
            parent_dept_code,
            location,
            location_name,
            sales_yn,
            company_name
          FROM dept_info
          WHERE dept_code = $1`,
          [user.dept_code]
        )
      : null;

    // 응답 데이터 가공
    const userInfo = {
      sabun: user.sabun,
      userId: user.user_id,
      userName: user.user_name,
      userNameEng: user.user_name_eng,
      userNameCn: user.user_name_cn,
      deptCode: user.dept_code,
      deptName: user.dept_name,
      positionCode: user.position_code,
      positionName: user.position_name,
      email: user.email,
      tel: user.tel,
      cellPhone: user.cell_phone,
      userType: user.user_type,
      userTypeName: user.user_type_name,
      regdate: user.regdate ? new Date(user.regdate).toISOString() : null,
      status: user.status || "active",
      endDate: user.end_date ? new Date(user.end_date).toISOString() : null,
      faxNo: user.fax_no,
      partnerObjid: user.partner_objid,
      rank: user.rank,
      photo: user.photo
        ? `data:image/jpeg;base64,${Buffer.from(user.photo).toString("base64")}`
        : null,
      locale: user.locale,
      companyCode: user.company_code,
      dataType: user.data_type,
      // 부서 정보
      deptInfo: {
        deptCode: user.dept_code,
        deptName: deptInfo?.dept_name,
        parentDeptCode: deptInfo?.parent_dept_code,
        location: deptInfo?.location,
        locationName: deptInfo?.location_name,
        salesYn: deptInfo?.sales_yn,
        companyName: deptInfo?.company_name,
      },
    };

    const response = {
      success: true,
      data: userInfo,
      message: "사용자 상세 정보 조회 성공",
    };

    logger.info("사용자 상세 정보 조회 성공", {
      userId,
      userName: user.user_name,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 상세 정보 조회 실패", {
      error,
      userId: req.params.userId,
    });
    res.status(500).json({
      success: false,
      message: "사용자 상세 정보 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * POST /api/admin/users/check-duplicate
 * 사용자 ID 중복 체크 API
 * 기존 Java AdminController의 checkDuplicateUserId 기능 포팅
 */
export const checkDuplicateUserId = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { userId } = req.body;
    logger.info(`사용자 ID 중복 체크 요청 - userId: ${userId}`, {
      user: req.user,
    });

    if (!userId) {
      res.status(400).json({
        success: false,
        message: "사용자 ID가 필요합니다.",
        error: {
          code: "USER_ID_REQUIRED",
          details: "userId is required",
        },
      });
      return;
    }

    // Raw Query로 사용자 ID 중복 체크
    const existingUser = await queryOne<any>(
      `SELECT user_id FROM user_info WHERE user_id = $1`,
      [userId]
    );

    const isDuplicate = !!existingUser;
    const count = isDuplicate ? 1 : 0;

    const response = {
      success: true,
      data: {
        isDuplicate,
        count,
        message: isDuplicate
          ? "이미 사용 중인 사용자 ID입니다."
          : "사용 가능한 사용자 ID입니다.",
      },
      message: "사용자 ID 중복 체크 완료",
    };

    logger.info("사용자 ID 중복 체크 완료", {
      userId,
      isDuplicate,
      count,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 ID 중복 체크 실패", {
      error,
      userId: req.body?.userId,
    });
    res.status(500).json({
      success: false,
      message: "사용자 ID 중복 체크 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * POST /api/admin/users
 * 사용자 등록/수정 API
 * 기존 Java AdminController의 saveUserInfo 기능 포팅
 */
/**
 * GET /api/admin/users/:userId/history
 * 사용자 변경이력 조회 API
 * 기존 Java AdminController.getUserHistory() 포팅
 */
export const getUserHistory = async (
  req: AuthenticatedRequest,
  res: Response
) => {
  try {
    const { userId } = req.params;
    const { page = 1, countPerPage = 10 } = req.query;

    logger.info(`사용자 변경이력 조회 요청 - userId: ${userId}`, {
      page,
      countPerPage,
      user: req.user,
    });

    if (!userId) {
      res.status(400).json({
        success: false,
        message: "사용자 ID가 필요합니다.",
        error: {
          code: "USER_ID_REQUIRED",
          details: "userId parameter is required",
        },
      });
      return;
    }

    // PostgreSQL 클라이언트 생성
    const client = new Client({
      connectionString:
        process.env.DATABASE_URL ||
        "postgresql://postgres:postgres@localhost:5432/ilshin",
    });

    await client.connect();

    try {
      // 페이징 계산
      const currentPage = Number(page);
      const pageSize = Number(countPerPage);
      const pageStart = (currentPage - 1) * pageSize + 1;
      const pageEnd = currentPage * pageSize;

      // 전체 건수 조회 쿼리 (기존 backend와 동일한 로직)
      const countQuery = `
        SELECT
          CEIL(TOTAL_CNT::float / $1)::integer AS MAX_PAGE_SIZE,
          TOTAL_CNT
        FROM (
          SELECT 
            COUNT(1) AS TOTAL_CNT
          FROM user_info_history
          WHERE user_id = $2
        ) A
      `;

      const countResult = await client.query(countQuery, [pageSize, userId]);
      const countData = countResult.rows[0] || {
        total_cnt: 0,
        max_page_size: 1,
      };

      // 변경이력 목록 조회 쿼리 (기존 backend와 동일한 로직)
      const historyQuery = `
        SELECT 
          A.*
        FROM (
          SELECT 
            A.*, 
            ROW_NUMBER() OVER (ORDER BY RM DESC) AS RNUM
          FROM (
            SELECT
              T.*,
              ROW_NUMBER() OVER (ORDER BY regdate) AS RM,
              (SELECT user_name FROM user_info UI WHERE T.writer = UI.user_id) AS writer_name,
              TO_CHAR(T.regdate, 'YYYY-MM-DD HH24:MI:SS') AS reg_date_title
            FROM 
              user_info_history T
            WHERE user_id = $1
          ) A
          WHERE 1=1
        ) A
        WHERE 1=1
        AND RNUM::integer <= $2
        AND RNUM::integer >= $3
        ORDER BY RM DESC
      `;

      const historyResult = await client.query(historyQuery, [
        userId,
        pageEnd,
        pageStart,
      ]);

      // 응답 데이터 가공
      const historyList = historyResult.rows.map((row) => ({
        sabun: row.sabun || "",
        userId: row.user_id || "",
        userName: row.user_name || "",
        deptCode: row.dept_code || "",
        deptName: row.dept_name || "",
        userTypeName: row.user_type_name || "",
        historyType: row.history_type || "",
        writer: row.writer || "",
        writerName: row.writer_name || "",
        regDate: row.regdate,
        regDateTitle: row.reg_date_title || "",
        status: row.status || "",
        rowNum: row.rnum,
      }));

      logger.info(
        `사용자 변경이력 조회 완료 - userId: ${userId}, 조회건수: ${historyList.length}, 전체: ${countData.total_cnt}`
      );

      const response: ApiResponse<any[]> = {
        success: true,
        data: historyList,
        total: Number(countData.total_cnt),

        pagination: {
          page: currentPage,
          limit: pageSize,
          total: Number(countData.total_cnt),
          totalPages: Number(countData.max_page_size),
        },
      };

      res.status(200).json(response);
    } finally {
      await client.end();
    }
  } catch (error) {
    logger.error("사용자 변경이력 조회 중 오류 발생", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "사용자 변경이력 조회 중 오류가 발생했습니다.",
      error: {
        code: "USER_HISTORY_FETCH_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
};

/**
 * PATCH /api/admin/users/:userId/status
 * 사용자 상태 변경 API (부분 수정)
 * 기존 Java AdminController.changeUserStatus() 포팅
 */
export const changeUserStatus = async (
  req: AuthenticatedRequest,
  res: Response
) => {
  try {
    const { userId } = req.params;
    const { status } = req.body;

    logger.info("사용자 상태 변경 요청", { userId, status, user: req.user });

    // 필수 파라미터 검증
    if (!userId || !status) {
      res.status(400).json({
        result: false,
        msg: "사용자 ID와 상태는 필수입니다.",
      });
      return;
    }

    // 상태 값 검증
    if (!["active", "inactive"].includes(status)) {
      res.status(400).json({
        result: false,
        msg: "유효하지 않은 상태값입니다. (active, inactive만 허용)",
      });
      return;
    }

    // Raw Query를 사용한 사용자 상태 변경
    // 1. 사용자 존재 여부 확인
    const currentUser = await queryOne<any>(
      `SELECT user_id, user_name, status FROM user_info WHERE user_id = $1`,
      [userId]
    );

    if (!currentUser) {
      res.status(404).json({
        result: false,
        msg: "사용자를 찾을 수 없습니다.",
      });
      return;
    }

    // 2. 상태 변경 실행
    // active/inactive에 따른 END_DATE 처리
    const endDate = status === "inactive" ? new Date() : null;

    const updateResult = await query<any>(
      `UPDATE user_info
       SET status = $1, end_date = $2
       WHERE user_id = $3
       RETURNING *`,
      [status, endDate, userId]
    );

    if (updateResult.length > 0) {
      // 사용자 이력 저장은 user_info_history 테이블이 @@ignore 상태이므로 생략

      logger.info("사용자 상태 변경 성공", {
        userId,
        oldStatus: currentUser.status,
        newStatus: status,
        updatedBy: req.user?.userId,
      });

      auditLogService.log({
        companyCode: currentUser.company_code || req.user?.companyCode || "",
        userId: req.user?.userId || "",
        userName: req.user?.userName || "",
        action: "STATUS_CHANGE",
        resourceType: "USER",
        resourceId: userId,
        resourceName: currentUser.user_name,
        summary: `사용자 "${currentUser.user_name}" 상태 변경: ${currentUser.status} → ${status}`,
        changes: { before: { status: currentUser.status }, after: { status }, fields: ["status"] },
        ipAddress: getClientIp(req),
        requestPath: req.originalUrl,
      });

      res.json({
        result: true,
        msg: `사용자 상태가 ${status === "active" ? "활성" : "비활성"}으로 변경되었습니다.`,
      });
    } else {
      res.status(400).json({
        result: false,
        msg: "사용자 상태 변경에 실패했습니다.",
      });
    }
  } catch (error) {
    logger.error("사용자 상태 변경 중 오류 발생", {
      error,
      userId: req.params.userId,
      status: req.body.status,
    });
    res.status(500).json({
      result: false,
      msg: "시스템 오류가 발생했습니다.",
    });
  }
};

export const saveUser = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userData = req.body;
    const isUpdate = req.method === "PUT"; // PUT 요청이면 수정

    logger.info("사용자 저장 요청", {
      userData,
      user: req.user,
      isUpdate,
      method: req.method,
    });

    // 필수 필드 검증
    let requiredFields = ["userId", "userName"];

    // 신규 등록 시에만 비밀번호 필수
    if (!isUpdate) {
      requiredFields.push("userPassword");
    }

    for (const field of requiredFields) {
      if (!userData[field] || userData[field].trim() === "") {
        res.status(400).json({
          success: false,
          message: `${field}는 필수 입력 항목입니다.`,
          error: {
            code: "REQUIRED_FIELD_MISSING",
            details: `Required field: ${field}`,
          },
        });
        return;
      }
    }

    // 비밀번호 암호화 (비밀번호가 제공된 경우에만)
    let encryptedPassword = null;
    if (userData.userPassword) {
      encryptedPassword = await EncryptUtil.encrypt(userData.userPassword);
    }

    // Raw Query를 사용한 사용자 저장 (upsert with ON CONFLICT)
    const updatePasswordClause = encryptedPassword ? "user_password = $4," : "";

    const [savedUser] = await query<any>(
      `INSERT INTO user_info (
        user_id, user_name, user_name_eng, user_password,
        dept_code, dept_name, position_code, position_name,
        email, tel, cell_phone, user_type, user_type_name,
        sabun, company_code, status, locale, regdate
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
      ON CONFLICT (user_id) DO UPDATE SET
        user_name = $2,
        user_name_eng = $3,
        ${updatePasswordClause}
        dept_code = $5,
        dept_name = $6,
        position_code = $7,
        position_name = $8,
        email = $9,
        tel = $10,
        cell_phone = $11,
        user_type = $12,
        user_type_name = $13,
        sabun = $14,
        company_code = $15,
        status = $16,
        locale = $17
      RETURNING *`,
      [
        userData.userId,
        userData.userName,
        userData.userNameEng || null,
        encryptedPassword || "", // 빈 문자열로 넣되, UPDATE에서는 조건부로 제외
        userData.deptCode || null,
        userData.deptName || null,
        userData.positionCode || null,
        userData.positionName || null,
        userData.email || null,
        userData.tel || null,
        userData.cellPhone || null,
        userData.userType || null,
        userData.userTypeName || null,
        userData.sabun || null,
        userData.companyCode || null,
        userData.status || "active",
        userData.locale || null,
        new Date(),
      ]
    );

    // 기존 사용자인지 새 사용자인지 확인 (regdate로 판단)
    const isExistingUser =
      savedUser.regdate &&
      new Date(savedUser.regdate).getTime() < Date.now() - 1000;

    logger.info(
      isExistingUser ? "사용자 정보 수정 완료" : "새 사용자 등록 완료",
      {
        userId: userData.userId,
      }
    );

    const response = {
      success: true,
      result: true,
      message: isExistingUser
        ? "사용자 정보가 수정되었습니다."
        : "사용자가 등록되었습니다.",
      data: {
        userId: userData.userId,
        isUpdate: isExistingUser,
      },
    };

    auditLogService.log({
      companyCode: userData.companyCode || req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: isExistingUser ? "UPDATE" : "CREATE",
      resourceType: "USER",
      resourceId: userData.userId,
      resourceName: userData.userName,
      summary: isExistingUser ? `사용자 "${userData.userName}" 정보 수정` : `사용자 "${userData.userName}" 등록`,
      changes: { after: { userId: userData.userId, userName: userData.userName, deptName: userData.deptName, status: userData.status } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("사용자 저장 실패", { error, userData: req.body });
    res.status(500).json({
      success: false,
      result: false,
      message: "사용자 저장 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * POST /api/admin/companies
 * 회사 등록 API
 * 기존 Java AdminController의 회사 등록 기능 포팅
 */
export const createCompany = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    logger.info("회사 등록 요청", {
      body: req.body,
      user: req.user,
    });

    const { company_name } = req.body;

    // 필수 입력값 검증
    if (!company_name || !company_name.trim()) {
      res.status(400).json({
        success: false,
        message: "회사명을 입력해주세요.",
        errorCode: "COMPANY_NAME_REQUIRED",
      });
      return;
    }

    // Raw Query로 회사명 중복 체크
    const existingCompany = await queryOne<any>(
      `SELECT company_code FROM company_mng WHERE company_name = $1`,
      [company_name.trim()]
    );

    // 사업자등록번호 유효성 검증
    const businessNumberValidation = validateBusinessNumber(
      req.body.business_registration_number?.trim() || ""
    );
    if (!businessNumberValidation.isValid) {
      res.status(400).json({
        success: false,
        message: businessNumberValidation.message,
        errorCode: "INVALID_BUSINESS_NUMBER",
      });
      return;
    }

    // Raw Query로 사업자등록번호 중복 체크
    const existingBusinessNumber = await queryOne<any>(
      `SELECT company_code FROM company_mng WHERE business_registration_number = $1`,
      [req.body.business_registration_number?.trim()]
    );

    if (existingCompany) {
      res.status(400).json({
        success: false,
        message: "이미 등록된 회사명입니다.",
        errorCode: "COMPANY_NAME_DUPLICATE",
      });
      return;
    }

    if (existingBusinessNumber) {
      res.status(400).json({
        success: false,
        message: "이미 등록된 사업자등록번호입니다.",
        errorCode: "DUPLICATE_BUSINESS_NUMBER",
      });
      return;
    }

    // PostgreSQL 클라이언트 생성 (복잡한 코드 생성 쿼리용)
    const client = new Client({
      connectionString:
        process.env.DATABASE_URL ||
        "postgresql://postgres:postgres@localhost:5432/ilshin",
    });

    await client.connect();

    try {
      // 회사 코드 생성 (COMPANY_1, COMPANY_2, ...)
      const codeQuery = `
        SELECT COALESCE(MAX(CAST(SUBSTRING(company_code FROM 9) AS INTEGER)), 0) + 1 as next_number
        FROM company_mng 
        WHERE company_code LIKE 'COMPANY_%'
      `;

      const codeResult = await client.query(codeQuery);
      const nextNumber = codeResult.rows[0].next_number;
      const companyCode = `COMPANY_${nextNumber}`;

      // 회사 정보 저장
      const insertQuery = `
        INSERT INTO company_mng (
          company_code, 
          company_name,
          business_registration_number,
          representative_name,
          representative_phone,
          email,
          website,
          address,
          writer, 
          regdate, 
          status
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
        RETURNING *
      `;

      const writer = req.user
        ? `${req.user.userName}(${req.user.userId})`
        : "시스템";
      const insertValues = [
        companyCode,
        company_name.trim(),
        req.body.business_registration_number?.trim() || null,
        req.body.representative_name?.trim() || null,
        req.body.representative_phone?.trim() || null,
        req.body.email?.trim() || null,
        req.body.website?.trim() || null,
        req.body.address?.trim() || null,
        writer,
        new Date(),
        "active",
      ];

      const insertResult = await client.query(insertQuery, insertValues);
      const createdCompany = insertResult.rows[0];

      // 회사 폴더 초기화 (파일 시스템)
      try {
        FileSystemManager.initializeCompanyFolder(createdCompany.company_code);
        logger.info("회사 폴더 초기화 완료", {
          companyCode: createdCompany.company_code,
        });
      } catch (folderError) {
        logger.warn("회사 폴더 초기화 실패 (회사 등록은 성공)", {
          companyCode: createdCompany.company_code,
          error: folderError,
        });
      }

      // 다국어 카테고리 자동 생성
      try {
        const { MultiLangService } = await import("../services/multilangService");
        const multilangService = new MultiLangService();
        await multilangService.ensureCompanyCategory(
          createdCompany.company_code,
          createdCompany.company_name
        );
        logger.info("회사 다국어 카테고리 생성 완료", {
          companyCode: createdCompany.company_code,
        });
      } catch (categoryError) {
        logger.warn("회사 다국어 카테고리 생성 실패 (회사 등록은 성공)", {
          companyCode: createdCompany.company_code,
          error: categoryError,
        });
      }

      logger.info("회사 등록 성공", {
        companyCode: createdCompany.company_code,
        companyName: createdCompany.company_name,
        writer: createdCompany.writer,
      });

      const response = {
        success: true,
        message: "회사가 성공적으로 등록되었습니다.",
        data: {
          company_code: createdCompany.company_code,
          company_name: createdCompany.company_name,
          writer: createdCompany.writer,
          regdate: createdCompany.regdate,
          status: createdCompany.status,
        },
      };

      auditLogService.log({
        companyCode: createdCompany.company_code,
        userId: req.user?.userId || "",
        userName: req.user?.userName || "",
        action: "CREATE",
        resourceType: "COMPANY",
        resourceId: createdCompany.company_code,
        resourceName: createdCompany.company_name,
        summary: `회사 "${createdCompany.company_name}" (${createdCompany.company_code}) 등록`,
        changes: { after: { company_code: createdCompany.company_code, company_name: createdCompany.company_name } },
        ipAddress: getClientIp(req),
        requestPath: req.originalUrl,
      });

      res.status(201).json(response);
    } finally {
      await client.end();
    }
  } catch (error) {
    logger.error("회사 등록 실패", { error, body: req.body });
    res.status(500).json({
      success: false,
      message: "회사 등록 중 오류가 발생했습니다.",
      errorCode: "COMPANY_CREATE_ERROR",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * GET /api/admin/companies/:companyCode
 * 회사 정보 조회 API
 */
export const getCompanyByCode = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { companyCode } = req.params;

    logger.info("회사 정보 조회 요청", {
      companyCode,
      user: req.user,
    });

    // Raw Query로 회사 정보 조회
    const company = await queryOne<any>(
      `SELECT * FROM company_mng WHERE company_code = $1`,
      [companyCode]
    );

    if (!company) {
      res.status(404).json({
        success: false,
        message: "해당 회사를 찾을 수 없습니다.",
        errorCode: "COMPANY_NOT_FOUND",
      });
      return;
    }

    logger.info("회사 정보 조회 성공", {
      companyCode: company.company_code,
      companyName: company.company_name,
    });

    const response = {
      success: true,
      message: "회사 정보 조회 성공",
      data: {
        companyCode: company.company_code,
        companyName: company.company_name,
        businessRegistrationNumber: company.business_registration_number,
        representativeName: company.representative_name,
        representativePhone: company.representative_phone,
        email: company.email,
        website: company.website,
        address: company.address,
        status: company.status,
        writer: company.writer,
        regdate: company.regdate,
      },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("회사 정보 조회 실패", {
      error,
      companyCode: req.params.companyCode,
    });
    res.status(500).json({
      success: false,
      message: "회사 정보 조회 중 오류가 발생했습니다.",
      errorCode: "COMPANY_GET_ERROR",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * PUT /api/admin/companies/:companyCode
 * 회사 정보 수정 API
 */
export const updateCompany = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { companyCode } = req.params;
    const {
      company_name,
      business_registration_number,
      representative_name,
      representative_phone,
      email,
      website,
      address,
      status,
    } = req.body;

    logger.info("회사 정보 수정 요청", {
      companyCode,
      body: req.body,
      user: req.user,
    });

    // 필수 입력값 검증
    if (!company_name || !company_name.trim()) {
      res.status(400).json({
        success: false,
        message: "회사명을 입력해주세요.",
        errorCode: "COMPANY_NAME_REQUIRED",
      });
      return;
    }

    // Raw Query로 회사명 중복 체크 (자기 자신 제외)
    const duplicateCompany = await queryOne<any>(
      `SELECT company_code FROM company_mng
       WHERE company_name = $1 AND company_code != $2`,
      [company_name.trim(), companyCode]
    );

    if (duplicateCompany) {
      res.status(400).json({
        success: false,
        message: "이미 등록된 회사명입니다.",
        errorCode: "COMPANY_NAME_DUPLICATE",
      });
      return;
    }

    // 사업자등록번호 중복 체크 및 유효성 검증 (자기 자신 제외)
    if (business_registration_number && business_registration_number.trim()) {
      // 유효성 검증
      const businessNumberValidation = validateBusinessNumber(
        business_registration_number.trim()
      );
      if (!businessNumberValidation.isValid) {
        res.status(400).json({
          success: false,
          message: businessNumberValidation.message,
          errorCode: "INVALID_BUSINESS_NUMBER",
        });
        return;
      }

      // 중복 체크
      const duplicateBusinessNumber = await queryOne<any>(
        `SELECT company_code FROM company_mng
         WHERE business_registration_number = $1 AND company_code != $2`,
        [business_registration_number.trim(), companyCode]
      );

      if (duplicateBusinessNumber) {
        res.status(400).json({
          success: false,
          message: "이미 등록된 사업자등록번호입니다.",
          errorCode: "DUPLICATE_BUSINESS_NUMBER",
        });
        return;
      }
    }

    const beforeCompany = await queryOne<any>(
      `SELECT company_name, status FROM company_mng WHERE company_code = $1`,
      [companyCode]
    );

    const result = await query<any>(
      `UPDATE company_mng
       SET 
         company_name = $1,
         business_registration_number = $2,
         representative_name = $3,
         representative_phone = $4,
         email = $5,
         website = $6,
         address = $7,
         status = $8
       WHERE company_code = $9
       RETURNING *`,
      [
        company_name.trim(),
        business_registration_number?.trim() || null,
        representative_name?.trim() || null,
        representative_phone?.trim() || null,
        email?.trim() || null,
        website?.trim() || null,
        address?.trim() || null,
        status || "active",
        companyCode,
      ]
    );

    if (result.length === 0) {
      res.status(404).json({
        success: false,
        message: "해당 회사를 찾을 수 없습니다.",
        errorCode: "COMPANY_NOT_FOUND",
      });
      return;
    }

    const updatedCompany = result[0];

    logger.info("회사 정보 수정 성공", {
      companyCode: updatedCompany.company_code,
      companyName: updatedCompany.company_name,
      status: updatedCompany.status,
    });

    const response = {
      success: true,
      message: "회사 정보가 수정되었습니다.",
      data: {
        company_code: updatedCompany.company_code,
        company_name: updatedCompany.company_name,
        writer: updatedCompany.writer,
        regdate: updatedCompany.regdate,
        status: updatedCompany.status,
      },
    };

    auditLogService.log({
      companyCode: updatedCompany.company_code,
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "UPDATE",
      resourceType: "COMPANY",
      resourceId: updatedCompany.company_code,
      resourceName: updatedCompany.company_name,
      summary: `회사 "${updatedCompany.company_name}" 정보 수정`,
      changes: {
        before: { company_name: beforeCompany?.company_name, status: beforeCompany?.status },
        after: { company_name: updatedCompany.company_name, status: updatedCompany.status },
      },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("회사 정보 수정 실패", { error, body: req.body });
    res.status(500).json({
      success: false,
      message: "회사 정보 수정 중 오류가 발생했습니다.",
      errorCode: "COMPANY_UPDATE_ERROR",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * DELETE /api/admin/companies/:companyCode
 * 회사 삭제 API
 */
export const deleteCompany = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { companyCode } = req.params;

    logger.info("회사 삭제 요청", {
      companyCode,
      user: req.user,
    });

    // Raw Query로 회사 삭제
    const result = await query<any>(
      `DELETE FROM company_mng
       WHERE company_code = $1
       RETURNING company_code, company_name`,
      [companyCode]
    );

    if (result.length === 0) {
      res.status(404).json({
        success: false,
        message: "해당 회사를 찾을 수 없습니다.",
        errorCode: "COMPANY_NOT_FOUND",
      });
      return;
    }

    const deletedCompany = result[0];

    logger.info("회사 삭제 성공", {
      companyCode: deletedCompany.company_code,
      companyName: deletedCompany.company_name,
    });

    const response = {
      success: true,
      message: "회사가 삭제되었습니다.",
      data: {
        company_code: deletedCompany.company_code,
        company_name: deletedCompany.company_name,
      },
    };

    auditLogService.log({
      companyCode: deletedCompany.company_code,
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "DELETE",
      resourceType: "COMPANY",
      resourceId: deletedCompany.company_code,
      resourceName: deletedCompany.company_name,
      summary: `회사 "${deletedCompany.company_name}" (${deletedCompany.company_code}) 삭제`,
      changes: { before: { company_code: deletedCompany.company_code, company_name: deletedCompany.company_name } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.status(200).json(response);
  } catch (error) {
    logger.error("회사 삭제 실패", { error });
    res.status(500).json({
      success: false,
      message: "회사 삭제 중 오류가 발생했습니다.",
      errorCode: "COMPANY_DELETE_ERROR",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
};

/**
 * POST /api/admin/users/reset-password
 * 사용자 비밀번호 초기화 API
 * 기존 Java AdminController.resetUserPassword() 포팅
 */
export const updateProfile = async (
  req: AuthenticatedRequest,
  res: Response
) => {
  try {
    const userId = req.user?.userId;
    if (!userId) {
      res.status(401).json({
        result: false,
        error: {
          code: "TOKEN_MISSING",
          details: "인증 토큰이 필요합니다.",
        },
      });
      return;
    }

    const {
      userName,
      userNameEng,
      userNameCn,
      email,
      tel,
      cellPhone,
      photo,
      locale,
    } = req.body;

    // 업데이트할 필드와 값 준비
    const updateFields: string[] = [];
    const updateValues: any[] = [];
    let paramIndex = 1;

    if (userName !== undefined) {
      updateFields.push(`user_name = $${paramIndex}`);
      updateValues.push(userName);
      paramIndex++;
    }
    if (userNameEng !== undefined) {
      updateFields.push(`user_name_eng = $${paramIndex}`);
      updateValues.push(userNameEng);
      paramIndex++;
    }
    if (userNameCn !== undefined) {
      updateFields.push(`user_name_cn = $${paramIndex}`);
      updateValues.push(userNameCn);
      paramIndex++;
    }
    if (email !== undefined) {
      updateFields.push(`email = $${paramIndex}`);
      updateValues.push(email);
      paramIndex++;
    }
    if (tel !== undefined) {
      updateFields.push(`tel = $${paramIndex}`);
      updateValues.push(tel);
      paramIndex++;
    }
    if (cellPhone !== undefined) {
      updateFields.push(`cell_phone = $${paramIndex}`);
      updateValues.push(cellPhone);
      paramIndex++;
    }

    // photo 데이터 처리 (Base64를 Buffer로 변환하여 저장)
    if (photo !== undefined) {
      if (photo && typeof photo === "string") {
        try {
          // Base64 헤더 제거 (data:image/jpeg;base64, 등)
          const base64Data = photo.replace(/^data:image\/[a-z]+;base64,/, "");
          // Base64를 Buffer로 변환
          updateFields.push(`photo = $${paramIndex}`);
          updateValues.push(Buffer.from(base64Data, "base64"));
          paramIndex++;
        } catch (error) {
          console.error("Base64 이미지 처리 오류:", error);
          updateFields.push(`photo = $${paramIndex}`);
          updateValues.push(null);
          paramIndex++;
        }
      } else {
        updateFields.push(`photo = $${paramIndex}`);
        updateValues.push(null);
        paramIndex++;
      }
    }

    if (locale !== undefined) {
      // language_master 테이블에서 유효한 언어 코드인지 확인
      const validLang = await queryOne<{ lang_code: string }>(
        "SELECT lang_code FROM language_master WHERE lang_code = $1 AND is_active = 'Y'",
        [locale]
      );

      if (!validLang) {
        res.status(400).json({
          result: false,
          error: {
            code: "INVALID_LOCALE",
            details: `유효하지 않은 로케일입니다: ${locale}`,
          },
        });
        return;
      }

      updateFields.push(`locale = $${paramIndex}`);
      updateValues.push(locale);
      paramIndex++;
    }

    // 업데이트할 데이터가 없으면 에러
    if (updateFields.length === 0) {
      res.status(400).json({
        result: false,
        error: {
          code: "NO_DATA",
          details: "업데이트할 데이터가 없습니다.",
        },
      });
      return;
    }

    // Raw Query로 데이터베이스 업데이트
    updateValues.push(userId);
    await query(
      `UPDATE user_info SET ${updateFields.join(", ")} WHERE user_id = $${paramIndex}`,
      updateValues
    );

    // 업데이트된 사용자 정보 조회
    const updatedUser = await queryOne<any>(
      `SELECT
        user_id, user_name, user_name_eng, user_name_cn,
        dept_code, dept_name, position_code, position_name,
        email, tel, cell_phone, user_type, user_type_name,
        photo, locale
      FROM user_info
      WHERE user_id = $1`,
      [userId]
    );

    // photo가 Buffer 타입인 경우 Base64로 변환
    const responseData = {
      ...updatedUser,
      photo: updatedUser?.photo
        ? `data:image/jpeg;base64,${Buffer.from(updatedUser.photo).toString("base64")}`
        : null,
    };

    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "UPDATE",
      resourceType: "USER",
      resourceId: userId,
      resourceName: updatedUser?.user_name || "",
      summary: `프로필 수정 (${updateFields.length}개 항목)`,
      changes: { after: { userName, email, tel, cellPhone, locale } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.json({
      result: true,
      message: "프로필이 성공적으로 업데이트되었습니다.",
      data: responseData,
    });
  } catch (error) {
    console.error("프로필 업데이트 오류:", error);
    res.status(500).json({
      result: false,
      error: {
        code: "UPDATE_FAILED",
        details: "프로필 업데이트 중 오류가 발생했습니다.",
      },
    });
  }
};

export const resetUserPassword = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  const { userId, newPassword } = req.body;

  logger.info("비밀번호 초기화 요청", { userId, user: req.user });

  // 입력값 검증
  if (!userId || !userId.trim()) {
    res.status(400).json({
      result: false,
      msg: "사용자 ID가 필요합니다.",
    });
    return;
  }

  if (!newPassword || !newPassword.trim()) {
    res.status(400).json({
      success: false,
      result: false,
      message: "새 비밀번호가 필요합니다.",
      msg: "새 비밀번호가 필요합니다.",
    });
    return;
  }

  // 비밀번호 길이 검증 (최소 4자)
  if (newPassword.length < 4) {
    res.status(400).json({
      success: false,
      result: false,
      message: "비밀번호는 최소 4자 이상이어야 합니다.",
      msg: "비밀번호는 최소 4자 이상이어야 합니다.",
    });
    return;
  }

  try {
    // 1. Raw Query로 사용자 존재 여부 확인
    const currentUser = await queryOne<any>(
      `SELECT user_id, user_name FROM user_info WHERE user_id = $1`,
      [userId]
    );

    if (!currentUser) {
      res.status(404).json({
        success: false,
        result: false,
        message: "사용자를 찾을 수 없습니다.",
        msg: "사용자를 찾을 수 없습니다.",
      });
      return;
    }

    // 2. 비밀번호 암호화 (기존 Java 로직과 동일)
    let encryptedPassword: string;
    try {
      // EncryptUtil과 동일한 암호화 사용
      const crypto = require("crypto");
      const keyName = "ILJIAESSECRETKEY";
      const algorithm = "aes-128-ecb";

      // AES-128-ECB 암호화
      const cipher = crypto.createCipher(algorithm, keyName);
      let encrypted = cipher.update(newPassword, "utf8", "hex");
      encrypted += cipher.final("hex");
      encryptedPassword = encrypted.toUpperCase();
    } catch (encryptError) {
      logger.error("비밀번호 암호화 중 오류 발생", {
        error: encryptError,
        userId,
      });
      res.status(500).json({
        success: false,
        result: false,
        message: "비밀번호 암호화 중 오류가 발생했습니다.",
        msg: "비밀번호 암호화 중 오류가 발생했습니다.",
      });
      return;
    }

    // 3. Raw Query로 비밀번호 업데이트 실행
    const updateResult = await query<any>(
      `UPDATE user_info SET user_password = $1 WHERE user_id = $2 RETURNING *`,
      [encryptedPassword, userId]
    );

    if (updateResult.length > 0) {
      // 이력 저장은 user_info_history 테이블이 @@ignore 상태이므로 생략

      logger.info("비밀번호 초기화 성공", {
        userId,
        updatedBy: req.user?.userId,
      });

      auditLogService.log({
        companyCode: req.user?.companyCode || "",
        userId: req.user?.userId || "",
        userName: req.user?.userName || "",
        action: "UPDATE",
        resourceType: "USER",
        resourceId: userId,
        resourceName: currentUser.user_name,
        summary: `사용자 "${currentUser.user_name}" 비밀번호 초기화`,
        changes: { fields: ["user_password"] },
        ipAddress: getClientIp(req),
        requestPath: req.originalUrl,
      });

      res.json({
        success: true,
        result: true,
        message: "비밀번호가 성공적으로 초기화되었습니다.",
        msg: "비밀번호가 성공적으로 초기화되었습니다.",
      });
    } else {
      res.status(400).json({
        success: false,
        result: false,
        message: "사용자 정보를 찾을 수 없거나 비밀번호 변경에 실패했습니다.",
        msg: "사용자 정보를 찾을 수 없거나 비밀번호 변경에 실패했습니다.",
      });
    }
  } catch (error) {
    logger.error("비밀번호 초기화 중 오류 발생", {
      error,
      userId,
    });
    res.status(500).json({
      success: false,
      result: false,
      message: "비밀번호 초기화 중 시스템 오류가 발생했습니다.",
      msg: "비밀번호 초기화 중 시스템 오류가 발생했습니다.",
    });
  }
};

/**
 * 테이블 스키마 조회 (엑셀 업로드 컬럼 매핑용)
 * table_type_columns 테이블에서 라벨 정보도 함께 가져옴
 */
export async function getTableSchema(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const companyCode = req.user?.companyCode;

    if (!tableName) {
      res.status(400).json({
        success: false,
        message: "테이블명이 필요합니다.",
      });
      return;
    }

    logger.info("테이블 스키마 조회", { tableName, companyCode });

    // information_schema와 table_type_columns를 JOIN하여 컬럼 정보 + 회사별 제약조건 함께 가져오기
    // 회사별 설정 우선, 없으면 공통(*) 설정 사용
    const schemaQuery = `
      SELECT 
        ic.column_name,
        ic.data_type,
        ic.is_nullable AS db_is_nullable,
        ic.column_default,
        ic.character_maximum_length,
        ic.numeric_precision,
        ic.numeric_scale,
        COALESCE(NULLIF(ttc_company.column_label, ic.column_name), ttc_common.column_label) AS column_label,
        COALESCE(ttc_company.display_order, ttc_common.display_order) AS display_order,
        COALESCE(ttc_company.is_nullable, ttc_common.is_nullable) AS ttc_is_nullable,
        COALESCE(ttc_company.is_unique, ttc_common.is_unique) AS ttc_is_unique
      FROM information_schema.columns ic
      LEFT JOIN table_type_columns ttc_common 
        ON ttc_common.table_name = ic.table_name 
        AND ttc_common.column_name = ic.column_name
        AND ttc_common.company_code = '*'
      LEFT JOIN table_type_columns ttc_company 
        ON ttc_company.table_name = ic.table_name 
        AND ttc_company.column_name = ic.column_name
        AND ttc_company.company_code = $2
      WHERE ic.table_schema = 'public'
        AND ic.table_name = $1
      ORDER BY COALESCE(ttc_company.display_order, ttc_common.display_order, ic.ordinal_position), ic.ordinal_position
    `;

    const columns = await query<any>(schemaQuery, [tableName, companyCode]);

    if (columns.length === 0) {
      res.status(404).json({
        success: false,
        message: `테이블 '${tableName}'을 찾을 수 없습니다.`,
      });
      return;
    }

    // 컬럼 정보를 간단한 형태로 변환 (회사별 제약조건 반영)
    const columnList = columns.map((col: any) => {
      // DB level nullable + 회사별 table_type_columns 제약조건 통합
      // table_type_columns에서 is_nullable = 'N'이면 필수 (DB가 nullable이어도)
      const dbNullable = col.db_is_nullable === "YES";
      const ttcNotNull = col.ttc_is_nullable === "N";
      const effectiveNullable = ttcNotNull ? false : dbNullable;

      const ttcUnique = col.ttc_is_unique === "Y";

      return {
        name: col.column_name,
        label: col.column_label || col.column_name,
        type: col.data_type,
        nullable: effectiveNullable,
        unique: ttcUnique,
        default: col.column_default,
        maxLength: col.character_maximum_length,
        precision: col.numeric_precision,
        scale: col.numeric_scale,
      };
    });

    logger.info(`테이블 스키마 조회 성공: ${columnList.length}개 컬럼`);

    res.json({
      success: true,
      message: "테이블 스키마 조회 성공",
      data: {
        tableName,
        columns: columnList,
      },
    });
  } catch (error) {
    logger.error("테이블 스키마 조회 중 오류 발생:", error);
    res.status(500).json({
      success: false,
      message: "테이블 스키마 조회 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_SCHEMA_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    });
  }
}

/**
 * 메뉴 복사
 * POST /api/admin/menus/:menuObjid/copy
 */
export async function copyMenu(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { menuObjid } = req.params;
    const { targetCompanyCode } = req.body;
    const userId = req.user!.userId;
    const userCompanyCode = req.user!.companyCode;
    const userType = req.user!.userType;
    const isSuperAdmin = req.user!.isSuperAdmin;

    logger.info(`
=== 메뉴 복사 API 호출 ===
  menuObjid: ${menuObjid}
  targetCompanyCode: ${targetCompanyCode}
  userId: ${userId}
  userCompanyCode: ${userCompanyCode}
  userType: ${userType}
  isSuperAdmin: ${isSuperAdmin}
    `);

    // 권한 체크: 최고 관리자만 가능
    if (!isSuperAdmin && userType !== "SUPER_ADMIN") {
      logger.warn(
        `권한 없음: ${userId} (userType=${userType}, company_code=${userCompanyCode})`
      );
      res.status(403).json({
        success: false,
        message: "메뉴 복사는 최고 관리자(SUPER_ADMIN)만 가능합니다",
        error: {
          code: "FORBIDDEN",
          details: "Only super admin can copy menus",
        },
      });
      return;
    }

    // 필수 파라미터 검증
    if (!menuObjid || !targetCompanyCode) {
      res.status(400).json({
        success: false,
        message: "필수 파라미터가 누락되었습니다",
        error: {
          code: "MISSING_PARAMETERS",
          details: "menuObjid and targetCompanyCode are required",
        },
      });
      return;
    }

    // 화면명 변환 설정 (선택사항)
    const screenNameConfig = req.body.screenNameConfig
      ? {
          removeText: req.body.screenNameConfig.removeText,
          addPrefix: req.body.screenNameConfig.addPrefix,
          replaceFrom: req.body.screenNameConfig.replaceFrom,
          replaceTo: req.body.screenNameConfig.replaceTo,
        }
      : undefined;

    // 추가 복사 옵션 (카테고리, 코드, 채번규칙 등)
    const additionalCopyOptions = req.body.additionalCopyOptions
      ? {
          copyCodeCategory: req.body.additionalCopyOptions.copyCodeCategory === true,
          copyNumberingRules: req.body.additionalCopyOptions.copyNumberingRules === true,
          copyCategoryMapping: req.body.additionalCopyOptions.copyCategoryMapping === true,
        }
      : undefined;

    // 메뉴 복사 실행
    const menuCopyService = new MenuCopyService();
    const result = await menuCopyService.copyMenu(
      parseInt(menuObjid, 10),
      targetCompanyCode,
      userId,
      screenNameConfig,
      additionalCopyOptions
    );

    logger.info("✅ 메뉴 복사 API 성공");

    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || userId,
      userName: req.user?.userName || "",
      action: "COPY",
      resourceType: "MENU",
      resourceId: menuObjid,
      summary: `메뉴(${menuObjid}) → 회사 "${targetCompanyCode}"로 복사`,
      changes: { after: { targetCompanyCode, menuObjid } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.json({
      success: true,
      message: "메뉴 복사 완료",
      data: result,
    });
  } catch (error: any) {
    logger.error("❌ 메뉴 복사 API 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 복사 중 오류가 발생했습니다",
      error: {
        code: "MENU_COPY_ERROR",
        details: error.message || "Unknown error",
      },
    });
  }
}

/**
 * ============================================================
 * 사원 + 부서 통합 관리 API
 * ============================================================
 * 
 * 사원 정보(user_info)와 부서 관계(user_dept)를 트랜잭션으로 동시 저장합니다.
 * 
 * ## 핵심 기능
 * 1. user_info 테이블에 사원 개인정보 저장
 * 2. user_dept 테이블에 메인 부서 + 겸직 부서 저장
 * 3. 메인 부서 변경 시 기존 메인 → 겸직으로 자동 전환
 * 4. 트랜잭션으로 데이터 정합성 보장
 * 
 * ## 요청 데이터 구조
 * ```json
 * {
 *   "userInfo": {
 *     "user_id": "string (필수)",
 *     "user_name": "string (필수)",
 *     "email": "string",
 *     "cell_phone": "string",
 *     "sabun": "string",
 *     ...
 *   },
 *   "mainDept": {
 *     "dept_code": "string (필수)",
 *     "dept_name": "string",
 *     "position_name": "string"
 *   },
 *   "subDepts": [
 *     {
 *       "dept_code": "string (필수)",
 *       "dept_name": "string",
 *       "position_name": "string"
 *     }
 *   ]
 * }
 * ```
 */

// 사원 + 부서 저장 요청 타입
interface UserWithDeptRequest {
  userInfo: {
    user_id: string;
    user_name: string;
    user_name_eng?: string;
    user_password?: string;
    email?: string;
    tel?: string;
    cell_phone?: string;
    sabun?: string;
    user_type?: string;
    user_type_name?: string;
    status?: string;
    locale?: string;
    // 메인 부서 정보 (user_info에도 저장)
    dept_code?: string;
    dept_name?: string;
    position_code?: string;
    position_name?: string;
  };
  mainDept?: {
    dept_code: string;
    dept_name?: string;
    position_name?: string;
  };
  subDepts?: Array<{
    dept_code: string;
    dept_name?: string;
    position_name?: string;
  }>;
  isUpdate?: boolean; // 수정 모드 여부
}

/**
 * POST /api/admin/users/with-dept
 * 사원 + 부서 통합 저장 API
 */
export const saveUserWithDept = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  const client = await getPool().connect();
  
  try {
    const { userInfo, mainDept, subDepts = [], isUpdate = false } = req.body as UserWithDeptRequest;
    const companyCode = req.user?.companyCode || "*";
    const currentUserId = req.user?.userId;

    logger.info("사원+부서 통합 저장 요청", {
      userId: userInfo?.user_id,
      mainDept: mainDept?.dept_code,
      subDeptsCount: subDepts.length,
      isUpdate,
      companyCode,
    });

    // 필수값 검증
    if (!userInfo?.user_id || !userInfo?.user_name) {
      res.status(400).json({
        success: false,
        message: "사용자 ID와 이름은 필수입니다.",
        error: { code: "REQUIRED_FIELD_MISSING" },
      });
      return;
    }

    // 트랜잭션 시작
    await client.query("BEGIN");

    // 1. 기존 사용자 확인
    const existingUser = await client.query(
      "SELECT user_id FROM user_info WHERE user_id = $1",
      [userInfo.user_id]
    );
    const isExistingUser = existingUser.rows.length > 0;

    // 2. 비밀번호 암호화 (새 사용자이거나 비밀번호가 제공된 경우)
    let encryptedPassword = null;
    if (userInfo.user_password) {
      encryptedPassword = await EncryptUtil.encrypt(userInfo.user_password);
    }

    // 3. user_info 저장 (UPSERT)
    // mainDept가 있으면 user_info에도 메인 부서 정보 저장
    const deptCode = mainDept?.dept_code || userInfo.dept_code || null;
    const deptName = mainDept?.dept_name || userInfo.dept_name || null;
    const positionName = mainDept?.position_name || userInfo.position_name || null;

    if (isExistingUser) {
      // 기존 사용자 수정
      const updateFields: string[] = [];
      const updateValues: any[] = [];
      let paramIndex = 1;

      // 동적으로 업데이트할 필드 구성
      const fieldsToUpdate: Record<string, any> = {
        user_name: userInfo.user_name,
        user_name_eng: userInfo.user_name_eng,
        email: userInfo.email,
        tel: userInfo.tel,
        cell_phone: userInfo.cell_phone,
        sabun: userInfo.sabun,
        user_type: userInfo.user_type,
        user_type_name: userInfo.user_type_name,
        status: userInfo.status || "active",
        locale: userInfo.locale,
        dept_code: deptCode,
        dept_name: deptName,
        position_code: userInfo.position_code,
        position_name: positionName,
        company_code: companyCode !== "*" ? companyCode : undefined,
      };

      // 비밀번호가 제공된 경우에만 업데이트
      if (encryptedPassword) {
        fieldsToUpdate.user_password = encryptedPassword;
      }

      for (const [key, value] of Object.entries(fieldsToUpdate)) {
        if (value !== undefined) {
          updateFields.push(`${key} = $${paramIndex}`);
          updateValues.push(value);
          paramIndex++;
        }
      }

      if (updateFields.length > 0) {
        updateValues.push(userInfo.user_id);
        await client.query(
          `UPDATE user_info SET ${updateFields.join(", ")} WHERE user_id = $${paramIndex}`,
          updateValues
        );
      }
    } else {
      // 새 사용자 등록
      await client.query(
        `INSERT INTO user_info (
          user_id, user_name, user_name_eng, user_password,
          email, tel, cell_phone, sabun,
          user_type, user_type_name, status, locale,
          dept_code, dept_name, position_code, position_name,
          company_code, regdate
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, NOW())`,
        [
          userInfo.user_id,
          userInfo.user_name,
          userInfo.user_name_eng || null,
          encryptedPassword || null,
          userInfo.email || null,
          userInfo.tel || null,
          userInfo.cell_phone || null,
          userInfo.sabun || null,
          userInfo.user_type || null,
          userInfo.user_type_name || null,
          userInfo.status || "active",
          userInfo.locale || null,
          deptCode,
          deptName,
          userInfo.position_code || null,
          positionName,
          companyCode !== "*" ? companyCode : null,
        ]
      );
    }

    // 4. user_dept 처리
    if (mainDept?.dept_code || subDepts.length > 0) {
      // 4-1. 기존 부서 관계 조회 (메인 부서 변경 감지용)
      const existingDepts = await client.query(
        "SELECT dept_code, is_primary FROM user_dept WHERE user_id = $1",
        [userInfo.user_id]
      );
      const existingMainDept = existingDepts.rows.find((d: any) => d.is_primary === true);

      // 4-2. 메인 부서가 변경된 경우, 기존 메인 부서를 겸직으로 전환
      if (mainDept?.dept_code && existingMainDept && existingMainDept.dept_code !== mainDept.dept_code) {
        logger.info("메인 부서 변경 감지 - 기존 메인을 겸직으로 전환", {
          userId: userInfo.user_id,
          oldMain: existingMainDept.dept_code,
          newMain: mainDept.dept_code,
        });
        
        await client.query(
          "UPDATE user_dept SET is_primary = false, updated_at = NOW() WHERE user_id = $1 AND dept_code = $2",
          [userInfo.user_id, existingMainDept.dept_code]
        );
      }

      // 4-3. 기존 겸직 부서 삭제 (메인 제외)
      // 새로 입력받은 subDepts로 교체하기 위해 기존 겸직 삭제
      await client.query(
        "DELETE FROM user_dept WHERE user_id = $1 AND is_primary = false",
        [userInfo.user_id]
      );

      // 4-4. 메인 부서 저장 (UPSERT)
      if (mainDept?.dept_code) {
        await client.query(
          `INSERT INTO user_dept (user_id, dept_code, is_primary, dept_name, user_name, position_name, company_code, created_at, updated_at)
           VALUES ($1, $2, true, $3, $4, $5, $6, NOW(), NOW())
           ON CONFLICT (user_id, dept_code) DO UPDATE SET
             is_primary = true,
             dept_name = $3,
             user_name = $4,
             position_name = $5,
             company_code = $6,
             updated_at = NOW()`,
          [
            userInfo.user_id,
            mainDept.dept_code,
            mainDept.dept_name || null,
            userInfo.user_name,
            mainDept.position_name || null,
            companyCode !== "*" ? companyCode : null,
          ]
        );
      }

      // 4-5. 겸직 부서 저장
      for (const subDept of subDepts) {
        if (!subDept.dept_code) continue;
        
        // 메인 부서와 같은 부서는 겸직으로 추가하지 않음
        if (mainDept?.dept_code === subDept.dept_code) continue;

        await client.query(
          `INSERT INTO user_dept (user_id, dept_code, is_primary, dept_name, user_name, position_name, company_code, created_at, updated_at)
           VALUES ($1, $2, false, $3, $4, $5, $6, NOW(), NOW())
           ON CONFLICT (user_id, dept_code) DO UPDATE SET
             is_primary = false,
             dept_name = $3,
             user_name = $4,
             position_name = $5,
             company_code = $6,
             updated_at = NOW()`,
          [
            userInfo.user_id,
            subDept.dept_code,
            subDept.dept_name || null,
            userInfo.user_name,
            subDept.position_name || null,
            companyCode !== "*" ? companyCode : null,
          ]
        );
      }
    }

    // 트랜잭션 커밋
    await client.query("COMMIT");

    logger.info("사원+부서 통합 저장 완료", {
      userId: userInfo.user_id,
      isUpdate: isExistingUser,
    });

    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: isExistingUser ? "UPDATE" : "CREATE",
      resourceType: "USER",
      resourceId: userInfo.user_id,
      resourceName: userInfo.user_name,
      summary: `사용자 "${userInfo.user_name}" ${isExistingUser ? "수정" : "등록"} (부서: ${mainDept?.dept_name || "없음"})`,
      changes: { after: { userName: userInfo.user_name, email: userInfo.email, deptName: mainDept?.dept_name, status: userInfo.status } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.json({
      success: true,
      message: isExistingUser ? "사원 정보가 수정되었습니다." : "사원이 등록되었습니다.",
      data: {
        userId: userInfo.user_id,
        isUpdate: isExistingUser,
      },
    });
  } catch (error: any) {
    // 트랜잭션 롤백
    await client.query("ROLLBACK");
    
    logger.error("사원+부서 통합 저장 실패", { error: error.message, body: req.body });
    
    // 중복 키 에러 처리
    if (error.code === "23505") {
      res.status(400).json({
        success: false,
        message: "이미 존재하는 사용자 ID입니다.",
        error: { code: "DUPLICATE_USER_ID" },
      });
      return;
    }

    res.status(500).json({
      success: false,
      message: "사원 저장 중 오류가 발생했습니다.",
      error: { code: "SAVE_ERROR", details: error.message },
    });
  } finally {
    client.release();
  }
}

/**
 * GET /api/admin/users/:userId/with-dept
 * 사원 + 부서 정보 조회 API (수정 모달용)
 */
export const getUserWithDept = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { userId } = req.params;
    const companyCode = req.user?.companyCode || "*";

    logger.info("사원+부서 조회 요청", { userId, companyCode });

    // 1. user_info 조회
    let userQuery = "SELECT * FROM user_info WHERE user_id = $1";
    const userParams: any[] = [userId];
    
    // 최고 관리자가 아니면 회사 필터링
    if (companyCode !== "*") {
      userQuery += " AND company_code = $2";
      userParams.push(companyCode);
    }

    const userResult = await query<any>(userQuery, userParams);
    
    if (userResult.length === 0) {
      res.status(404).json({
        success: false,
        message: "사용자를 찾을 수 없습니다.",
        error: { code: "USER_NOT_FOUND" },
      });
      return;
    }

    const userInfo = userResult[0];

    // 2. user_dept 조회 (메인 + 겸직)
    let deptQuery = "SELECT * FROM user_dept WHERE user_id = $1 ORDER BY is_primary DESC, created_at ASC";
    const deptResult = await query<any>(deptQuery, [userId]);

    const mainDept = deptResult.find((d: any) => d.is_primary === true);
    const subDepts = deptResult.filter((d: any) => d.is_primary === false);

    res.json({
      success: true,
      data: {
        userInfo,
        mainDept: mainDept || null,
        subDepts,
      },
    });
  } catch (error: any) {
    logger.error("사원+부서 조회 실패", { error: error.message, userId: req.params.userId });
    res.status(500).json({
      success: false,
      message: "사원 조회 중 오류가 발생했습니다.",
      error: { code: "QUERY_ERROR", details: error.message },
    });
  }
}
