import { Request, Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import multer from "multer";
import path from "path";
import fs from "fs";
import { generateUUID } from "../utils/generateId";
import { query, queryOne } from "../database/db";

// 임시 토큰 저장소 (메모리 기반, 실제 운영에서는 Redis 사용 권장)
const tempTokens = new Map<string, { objid: string; expires: number }>();

// 업로드 디렉토리 설정 (회사별로 분리)
const baseUploadDir = path.join(process.cwd(), "uploads");

// 디렉토리 생성 함수 (에러 핸들링 포함)
const ensureUploadDir = () => {
  try {
    if (!fs.existsSync(baseUploadDir)) {
      fs.mkdirSync(baseUploadDir, { recursive: true });
    }
  } catch (error) {
    console.warn(
      `업로드 디렉토리 생성 실패: ${error}. 기존 디렉토리를 사용합니다.`
    );
  }
};

// 초기화 시 디렉토리 확인
ensureUploadDir();

// 회사별 + 날짜별 디렉토리 생성 함수
const getCompanyUploadDir = (companyCode: string, dateFolder?: string) => {
  // 회사코드가 *인 경우 company_*로 변환
  const actualCompanyCode = companyCode === "*" ? "company_*" : companyCode;

  // 날짜 폴더가 제공되지 않은 경우 오늘 날짜 사용 (YYYY/MM/DD 형식)
  if (!dateFolder) {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0");
    const day = String(today.getDate()).padStart(2, "0");
    dateFolder = `${year}/${month}/${day}`;
  }

  const companyDir = path.join(baseUploadDir, actualCompanyCode, dateFolder);
  if (!fs.existsSync(companyDir)) {
    fs.mkdirSync(companyDir, { recursive: true });
  }
  return companyDir;
};

// Multer 설정
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    // 임시 디렉토리에 저장 (나중에 올바른 위치로 이동)
    const tempDir = path.join(baseUploadDir, "temp");
    if (!fs.existsSync(tempDir)) {
      fs.mkdirSync(tempDir, { recursive: true });
    }
    cb(null, tempDir);
  },
  filename: (req, file, cb) => {
    // 타임스탬프_원본파일명 형태로 저장 (회사코드는 디렉토리로 분리됨)
    const timestamp = Date.now();

    console.log("📁 파일명 처리:", {
      originalname: file.originalname,
      encoding: file.encoding,
      mimetype: file.mimetype,
    });

    // UTF-8 인코딩 문제 해결: Buffer를 통한 올바른 디코딩
    let decodedName;
    try {
      // 파일명이 깨진 경우 Buffer를 통해 올바르게 디코딩
      const buffer = Buffer.from(file.originalname, "latin1");
      decodedName = buffer.toString("utf8");
      console.log("📁 파일명 디코딩:", {
        original: file.originalname,
        decoded: decodedName,
      });
    } catch (error) {
      // 디코딩 실패 시 원본 사용
      decodedName = file.originalname;
      console.log("📁 파일명 디코딩 실패, 원본 사용:", file.originalname);
    }

    // 한국어를 포함한 유니코드 문자 보존하면서 안전한 파일명 생성
    // 위험한 문자만 제거: / \ : * ? " < > |
    const sanitizedName = decodedName
      .replace(/[\/\\:*?"<>|]/g, "_") // 파일시스템에서 금지된 문자만 치환
      .replace(/\s+/g, "_") // 공백을 언더스코어로 치환
      .replace(/_{2,}/g, "_"); // 연속된 언더스코어를 하나로 축약

    const savedFileName = `${timestamp}_${sanitizedName}`;

    console.log("📁 파일명 변환:", {
      original: file.originalname,
      sanitized: sanitizedName,
      saved: savedFileName,
    });

    cb(null, savedFileName);
  },
});

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 50 * 1024 * 1024, // 50MB 제한
  },
  fileFilter: (req, file, cb) => {
    // 프론트엔드에서 전송된 accept 정보 확인
    const acceptHeader = req.body?.accept;

    // 프론트엔드에서 */* 또는 * 허용한 경우 모든 파일 허용
    if (
      acceptHeader &&
      (acceptHeader.includes("*/*") || acceptHeader.includes("*"))
    ) {
      cb(null, true);
      return;
    }

    // 기본 허용 파일 타입
    const defaultAllowedTypes = [
      // 이미지 파일
      "image/jpeg",
      "image/png",
      "image/gif",
      "image/webp",
      "image/svg+xml",
      // 텍스트 파일
      "text/html",
      "text/plain",
      "text/markdown",
      "text/csv",
      "application/json",
      "application/xml",
      // PDF 파일
      "application/pdf",
      // Microsoft Office 파일
      "application/msword", // .doc
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
      "application/vnd.ms-excel", // .xls
      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx
      "application/vnd.ms-powerpoint", // .ppt
      "application/vnd.openxmlformats-officedocument.presentationml.presentation", // .pptx
      // 한컴오피스 파일
      "application/x-hwp", // .hwp (한글)
      "application/haansofthwp", // .hwp (다른 MIME 타입)
      "application/vnd.hancom.hwp", // .hwp (또 다른 MIME 타입)
      "application/vnd.hancom.hwpx", // .hwpx (한글 2014+)
      "application/x-hwpml", // .hwpml (한글 XML)
      "application/vnd.hancom.hcdt", // .hcdt (한셀)
      "application/vnd.hancom.hpt", // .hpt (한쇼)
      "application/octet-stream", // .hwp, .hwpx (일반적인 바이너리 파일)
      // 압축 파일
      "application/zip",
      "application/x-zip-compressed",
      "application/x-rar-compressed",
      "application/x-7z-compressed",
      // 미디어 파일
      "video/mp4",
      "video/webm",
      "video/ogg",
      "audio/mp3",
      "audio/mpeg",
      "audio/wav",
      "audio/ogg",
      // Apple/맥 파일
      "application/vnd.apple.pages", // .pages (Pages)
      "application/vnd.apple.numbers", // .numbers (Numbers)
      "application/vnd.apple.keynote", // .keynote (Keynote)
      "application/x-iwork-pages-sffpages", // .pages (다른 MIME)
      "application/x-iwork-numbers-sffnumbers", // .numbers (다른 MIME)
      "application/x-iwork-keynote-sffkey", // .keynote (다른 MIME)
      "application/vnd.apple.installer+xml", // .pkg (맥 설치 파일)
      "application/x-apple-diskimage", // .dmg (맥 디스크 이미지)
      // 기타 문서
      "application/rtf", // .rtf
      "application/vnd.oasis.opendocument.text", // .odt
      "application/vnd.oasis.opendocument.spreadsheet", // .ods
      "application/vnd.oasis.opendocument.presentation", // .odp
    ];

    if (defaultAllowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error("허용되지 않는 파일 타입입니다."));
    }
  },
});

/**
 * 파일 업로드 및 attach_file_info 테이블에 저장
 */
export const uploadFiles = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    if (!req.files || (req.files as Express.Multer.File[]).length === 0) {
      res.status(400).json({
        success: false,
        message: "업로드할 파일이 없습니다.",
      });
      return;
    }

    const files = req.files as Express.Multer.File[];

    const {
      docType = "DOCUMENT",
      docTypeName = "일반 문서",
      targetObjid,
      parentTargetObjid,
      // 테이블 연결 정보 (새로 추가)
      linkedTable,
      linkedField,
      recordId,
      autoLink,
      // 가상 파일 컬럼 정보
      columnName,
      isVirtualFileColumn,
    } = req.body;

    // 회사코드와 작성자 정보 결정 (우선순위: 요청 body > 사용자 토큰 정보 > 기본값)
    const companyCode =
      req.body.companyCode || (req.user as any)?.companyCode || "DEFAULT";
    const writer = req.body.writer || (req.user as any)?.userId || "system";

    // 자동 연결 로직 - target_objid 자동 생성
    let finalTargetObjid = targetObjid;
    
    // 🔑 템플릿 파일(screen_files:)이나 temp_ 파일은 autoLink 무시
    const isTemplateFile = targetObjid && (targetObjid.startsWith('screen_files:') || targetObjid.startsWith('temp_'));
    
    if (!isTemplateFile && autoLink === "true" && linkedTable && recordId) {
      // 가상 파일 컬럼의 경우 컬럼명도 포함한 target_objid 생성
      if (isVirtualFileColumn === "true" && columnName) {
        finalTargetObjid = `${linkedTable}:${recordId}:${columnName}`;
      } else {
        finalTargetObjid = `${linkedTable}:${recordId}`;
      }
    }

    const savedFiles = [];

    for (const file of files) {
      // 파일명 디코딩 (파일 저장 시와 동일한 로직)
      let decodedOriginalName;
      try {
        const buffer = Buffer.from(file.originalname, "latin1");
        decodedOriginalName = buffer.toString("utf8");
        console.log("💾 DB 저장용 파일명 디코딩:", {
          original: file.originalname,
          decoded: decodedOriginalName,
        });
      } catch (error) {
        decodedOriginalName = file.originalname;
        console.log(
          "💾 DB 저장용 파일명 디코딩 실패, 원본 사용:",
          file.originalname
        );
      }

      // 파일 확장자 추출
      const fileExt = path
        .extname(decodedOriginalName)
        .toLowerCase()
        .replace(".", "");

      // 파일 경로 설정 (회사별 + 날짜별 디렉토리 구조 반영)
      const today = new Date();
      const year = today.getFullYear();
      const month = String(today.getMonth() + 1).padStart(2, "0");
      const day = String(today.getDate()).padStart(2, "0");
      const dateFolder = `${year}/${month}/${day}`;

      // 회사코드가 *인 경우 company_*로 변환
      const actualCompanyCode = companyCode === "*" ? "company_*" : companyCode;

      // 임시 파일을 최종 위치로 이동
      const tempFilePath = file.path; // Multer가 저장한 임시 파일 경로
      const finalUploadDir = getCompanyUploadDir(companyCode, dateFolder);
      const finalFilePath = path.join(finalUploadDir, file.filename);

      // 파일 이동
      fs.renameSync(tempFilePath, finalFilePath);

      // DB에 저장할 경로 (실제 파일 위치와 일치)
      const relativePath = `/${actualCompanyCode}/${dateFolder}/${file.filename}`;
      const fullFilePath = `/uploads${relativePath}`;

      // attach_file_info 테이블에 저장
      const objidValue = parseInt(
        generateUUID().replace(/-/g, "").substring(0, 15),
        16
      );

      const [fileRecord] = await query<any>(
        `INSERT INTO attach_file_info (
          objid, target_objid, saved_file_name, real_file_name, doc_type, doc_type_name,
          file_size, file_ext, file_path, company_code, writer, regdate, status, parent_target_objid
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
        RETURNING *`,
        [
          objidValue,
          finalTargetObjid,
          file.filename,
          decodedOriginalName,
          docType,
          docTypeName,
          file.size,
          fileExt,
          fullFilePath,
          companyCode,
          writer,
          new Date(),
          "ACTIVE",
          parentTargetObjid,
        ]
      );

      savedFiles.push({
        objid: fileRecord.objid.toString(),
        savedFileName: fileRecord.saved_file_name,
        realFileName: fileRecord.real_file_name,
        fileSize: Number(fileRecord.file_size),
        fileExt: fileRecord.file_ext,
        filePath: fileRecord.file_path,
        docType: fileRecord.doc_type,
        docTypeName: fileRecord.doc_type_name,
        targetObjid: fileRecord.target_objid,
        parentTargetObjid: fileRecord.parent_target_objid,
        companyCode: companyCode, // 실제 전달받은 회사코드
        writer: fileRecord.writer,
        regdate: fileRecord.regdate?.toISOString(),
        status: fileRecord.status,
      });
    }

    // 🆕 레코드 모드: 해당 행의 attachments 컬럼 자동 업데이트
    const isRecordMode = req.body.isRecordMode === "true" || req.body.isRecordMode === true;
    
    // 🔍 디버깅: 레코드 모드 조건 확인
    console.log("🔍 [파일 업로드] 레코드 모드 조건 확인:", {
      isRecordMode,
      linkedTable,
      recordId,
      columnName,
      finalTargetObjid,
      "req.body.isRecordMode": req.body.isRecordMode,
      "req.body.linkedTable": req.body.linkedTable,
      "req.body.recordId": req.body.recordId,
      "req.body.columnName": req.body.columnName,
    });
    
    if (isRecordMode && linkedTable && recordId && columnName) {
      try {
        // 해당 레코드의 모든 첨부파일 조회
        const allFiles = await query<any>(
          `SELECT objid, real_file_name, file_size, file_ext, file_path, regdate
           FROM attach_file_info
           WHERE target_objid = $1 AND status = 'ACTIVE'
           ORDER BY regdate DESC`,
          [finalTargetObjid]
        );

        // attachments JSONB 형태로 변환
        const attachmentsJson = allFiles.map((f: any) => ({
          objid: f.objid.toString(),
          realFileName: f.real_file_name,
          fileSize: Number(f.file_size),
          fileExt: f.file_ext,
          filePath: f.file_path,
          regdate: f.regdate?.toISOString(),
        }));

        // 해당 테이블의 attachments 컬럼 업데이트
        // 🔒 멀티테넌시: company_code 필터 추가
        await query(
          `UPDATE ${linkedTable} 
           SET ${columnName} = $1::jsonb, updated_date = NOW()
           WHERE id = $2 AND company_code = $3`,
          [JSON.stringify(attachmentsJson), recordId, companyCode]
        );

        console.log("📎 [레코드 모드] attachments 컬럼 자동 업데이트:", {
          tableName: linkedTable,
          recordId: recordId,
          columnName: columnName,
          fileCount: attachmentsJson.length,
        });
      } catch (updateError) {
        // attachments 컬럼 업데이트 실패해도 파일 업로드는 성공으로 처리
        console.warn("⚠️ attachments 컬럼 업데이트 실패 (무시):", updateError);
      }
    }

    res.json({
      success: true,
      message: `${files.length}개 파일 업로드 완료`,
      files: savedFiles,
    });
  } catch (error) {
    console.error("파일 업로드 오류:", error);
    res.status(500).json({
      success: false,
      message: "파일 업로드 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "알 수 없는 오류",
    });
  }
};

/**
 * 파일 삭제 (논리적 삭제)
 */
export const deleteFile = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { objid } = req.params;
    const { writer = "system" } = req.body;

    // 🔒 멀티테넌시: 현재 사용자의 회사 코드
    const companyCode = req.user?.companyCode;

    // 파일 정보 조회
    const fileRecord = await queryOne<any>(
      `SELECT * FROM attach_file_info WHERE objid = $1`,
      [objid]
    );

    if (!fileRecord) {
      res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
      return;
    }

    // 🔒 멀티테넌시: 회사 코드 일치 여부 확인 (최고 관리자 제외)
    if (companyCode !== "*" && fileRecord.company_code !== companyCode) {
      console.warn("⚠️ 다른 회사 파일 삭제 시도:", {
        userId: req.user?.userId,
        userCompanyCode: companyCode,
        fileCompanyCode: fileRecord.company_code,
        objid,
      });
      res.status(403).json({
        success: false,
        message: "접근 권한이 없습니다.",
      });
      return;
    }

    // 파일 상태를 DELETED로 변경 (논리적 삭제)
    await query<any>(
      "UPDATE attach_file_info SET status = $1 WHERE objid = $2",
      ["DELETED", objid]
    );

    // 🆕 레코드 모드: 해당 행의 attachments 컬럼 자동 업데이트
    const targetObjid = fileRecord.target_objid;
    if (targetObjid && !targetObjid.startsWith('screen_files:') && !targetObjid.startsWith('temp_')) {
      // targetObjid 파싱: tableName:recordId:columnName 형식
      const parts = targetObjid.split(':');
      if (parts.length >= 3) {
        const [tableName, recordId, columnName] = parts;
        
        try {
          // 해당 레코드의 남은 첨부파일 조회
          const remainingFiles = await query<any>(
            `SELECT objid, real_file_name, file_size, file_ext, file_path, regdate
             FROM attach_file_info
             WHERE target_objid = $1 AND status = 'ACTIVE'
             ORDER BY regdate DESC`,
            [targetObjid]
          );

          // attachments JSONB 형태로 변환
          const attachmentsJson = remainingFiles.map((f: any) => ({
            objid: f.objid.toString(),
            realFileName: f.real_file_name,
            fileSize: Number(f.file_size),
            fileExt: f.file_ext,
            filePath: f.file_path,
            regdate: f.regdate?.toISOString(),
          }));

          // 해당 테이블의 attachments 컬럼 업데이트
          // 🔒 멀티테넌시: company_code 필터 추가
          await query(
            `UPDATE ${tableName} 
             SET ${columnName} = $1::jsonb, updated_date = NOW()
             WHERE id = $2 AND company_code = $3`,
            [JSON.stringify(attachmentsJson), recordId, fileRecord.company_code]
          );

          console.log("📎 [파일 삭제] attachments 컬럼 자동 업데이트:", {
            tableName,
            recordId,
            columnName,
            remainingFiles: attachmentsJson.length,
          });
        } catch (updateError) {
          // attachments 컬럼 업데이트 실패해도 파일 삭제는 성공으로 처리
          console.warn("⚠️ attachments 컬럼 업데이트 실패 (무시):", updateError);
        }
      }
    }

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

/**
 * 테이블 연결된 파일 조회
 */
export const getLinkedFiles = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { tableName, recordId } = req.params;

    // target_objid 생성 (테이블명:레코드ID 형식)
    const baseTargetObjid = `${tableName}:${recordId}`;

    // 기본 target_objid와 파일 컬럼 패턴 모두 조회 (tableName:recordId% 패턴)
    const files = await query<any>(
      `SELECT * FROM attach_file_info
       WHERE target_objid LIKE $1 AND status = $2
       ORDER BY regdate DESC`,
      [`${baseTargetObjid}%`, "ACTIVE"]
    );

    const fileList = files.map((file: any) => ({
      objid: file.objid.toString(),
      savedFileName: file.saved_file_name,
      realFileName: file.real_file_name,
      fileSize: Number(file.file_size),
      fileExt: file.file_ext,
      filePath: file.file_path,
      docType: file.doc_type,
      docTypeName: file.doc_type_name,
      targetObjid: file.target_objid,
      parentTargetObjid: file.parent_target_objid,
      writer: file.writer,
      regdate: file.regdate?.toISOString(),
      status: file.status,
    }));

    res.json({
      success: true,
      files: fileList,
      totalCount: fileList.length,
      targetObjid: baseTargetObjid, // 기준 target_objid 반환
    });
  } catch (error) {
    console.error("연결된 파일 조회 오류:", error);
    res.status(500).json({
      success: false,
      message: "연결된 파일 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "알 수 없는 오류",
    });
  }
};

/**
 * 파일 목록 조회
 */
export const getFileList = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { targetObjid, docType, companyCode } = req.query;

    const whereConditions: string[] = ["status = $1"];
    const queryParams: any[] = ["ACTIVE"];
    let paramIndex = 2;

    if (targetObjid) {
      whereConditions.push(`target_objid = $${paramIndex}`);
      queryParams.push(targetObjid as string);
      paramIndex++;
    }

    if (docType) {
      whereConditions.push(`doc_type = $${paramIndex}`);
      queryParams.push(docType as string);
      paramIndex++;
    }

    const files = await query<any>(
      `SELECT * FROM attach_file_info
       WHERE ${whereConditions.join(" AND ")}
       ORDER BY regdate DESC`,
      queryParams
    );

    const fileList = files.map((file: any) => ({
      objid: file.objid.toString(),
      savedFileName: file.saved_file_name,
      realFileName: file.real_file_name,
      fileSize: Number(file.file_size),
      fileExt: file.file_ext,
      filePath: file.file_path,
      docType: file.doc_type,
      docTypeName: file.doc_type_name,
      targetObjid: file.target_objid,
      parentTargetObjid: file.parent_target_objid,
      writer: file.writer,
      regdate: file.regdate?.toISOString(),
      status: file.status,
    }));

    res.json({
      success: true,
      files: fileList,
    });
  } catch (error) {
    console.error("파일 목록 조회 오류:", error);
    res.status(500).json({
      success: false,
      message: "파일 목록 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "알 수 없는 오류",
    });
  }
};

/**
 * 컴포넌트의 템플릿 파일과 데이터 파일을 모두 조회
 */
export const getComponentFiles = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { screenId, componentId, tableName, recordId, columnName } =
      req.query;

    // 🔒 멀티테넌시: 현재 사용자의 회사 코드 가져오기
    const companyCode = req.user?.companyCode;

    console.log("📂 [getComponentFiles] API 호출:", {
      screenId,
      componentId,
      tableName,
      recordId,
      columnName,
      user: req.user?.userId,
      companyCode, // 🔒 멀티테넌시 로그
    });

    if (!screenId || !componentId) {
      console.log("❌ [getComponentFiles] 필수 파라미터 누락");
      res.status(400).json({
        success: false,
        message: "screenId와 componentId가 필요합니다.",
      });
      return;
    }

    // 1. 템플릿 파일 조회 (화면 설계 시 업로드한 파일들)
    const templateTargetObjid = `screen_files:${screenId}:${componentId}:${columnName || "field_1"}`;
    console.log("🔍 [getComponentFiles] 템플릿 파일 조회:", {
      templateTargetObjid,
    });

    // 🔒 멀티테넌시: 회사별 필터링 추가
    const templateFiles = await query<any>(
      `SELECT * FROM attach_file_info
       WHERE target_objid = $1 AND status = $2 AND company_code = $3
       ORDER BY regdate DESC`,
      [templateTargetObjid, "ACTIVE", companyCode]
    );

    console.log(
      "📁 [getComponentFiles] 템플릿 파일 결과 (회사별 필터링):",
      templateFiles.length
    );

    // 2. 데이터 파일 조회 (실제 레코드와 연결된 파일들)
    let dataFiles: any[] = [];
    if (tableName && recordId && columnName) {
      const dataTargetObjid = `${tableName}:${recordId}:${columnName}`;
      // 🔒 멀티테넌시: 회사별 필터링 추가
      dataFiles = await query<any>(
        `SELECT * FROM attach_file_info
         WHERE target_objid = $1 AND status = $2 AND company_code = $3
         ORDER BY regdate DESC`,
        [dataTargetObjid, "ACTIVE", companyCode]
      );
    }

    // 3. 레코드의 컬럼 값으로 파일 직접 조회 (수정 모달에서 기존 파일 로드)
    // target_objid 매칭이 안 될 때, 테이블 레코드의 컬럼 값(파일 objid)으로 직접 찾기
    if (dataFiles.length === 0 && templateFiles.length === 0 && tableName && recordId && columnName) {
      try {
        // 레코드에서 해당 컬럼 값 조회 (파일 objid가 저장되어 있을 수 있음)
        const safeTable = String(tableName).replace(/[^a-zA-Z0-9_]/g, "");
        const safeColumn = String(columnName).replace(/[^a-zA-Z0-9_]/g, "");
        const recordResult = await query<any>(
          `SELECT "${safeColumn}" FROM "${safeTable}" WHERE id = $1 LIMIT 1`,
          [recordId]
        );
        
        if (recordResult.length > 0 && recordResult[0][safeColumn]) {
          const columnValue = String(recordResult[0][safeColumn]);
          // 숫자값인 경우 파일 objid로 간주하고 조회
          if (/^\d+$/.test(columnValue)) {
            console.log("🔍 [getComponentFiles] 레코드 컬럼 값으로 파일 조회:", { table: safeTable, column: safeColumn, fileObjid: columnValue });
            const directFiles = await query<any>(
              `SELECT * FROM attach_file_info
               WHERE objid = $1 AND status = $2
               ORDER BY regdate DESC`,
              [columnValue, "ACTIVE"]
            );
            if (directFiles.length > 0) {
              console.log("✅ [getComponentFiles] 레코드 컬럼 값으로 파일 찾음:", directFiles.length, "건");
              dataFiles = directFiles;
            }
          }
        }
      } catch (lookupError) {
        console.warn("⚠️ [getComponentFiles] 레코드 컬럼 값 조회 실패:", lookupError);
      }
    }

    // 파일 정보 포맷팅 함수
    const formatFileInfo = (file: any, isTemplate: boolean = false) => ({
      objid: file.objid.toString(),
      savedFileName: file.saved_file_name,
      realFileName: file.real_file_name,
      fileSize: Number(file.file_size),
      fileExt: file.file_ext,
      filePath: file.file_path,
      docType: file.doc_type,
      docTypeName: file.doc_type_name,
      targetObjid: file.target_objid,
      parentTargetObjid: file.parent_target_objid,
      writer: file.writer,
      regdate: file.regdate?.toISOString(),
      status: file.status,
      isTemplate, // 템플릿 파일 여부 표시
      isRepresentative: file.is_representative || false, // 대표 파일 여부
    });

    const formattedTemplateFiles = templateFiles.map((file) =>
      formatFileInfo(file, true)
    );
    const formattedDataFiles = dataFiles.map((file) =>
      formatFileInfo(file, false)
    );

    // 3. 전체 파일 목록 (데이터 파일 우선, 없으면 템플릿 파일 표시)
    const totalFiles =
      formattedDataFiles.length > 0
        ? formattedDataFiles
        : formattedTemplateFiles;

    res.json({
      success: true,
      templateFiles: formattedTemplateFiles,
      dataFiles: formattedDataFiles,
      totalFiles,
      summary: {
        templateCount: formattedTemplateFiles.length,
        dataCount: formattedDataFiles.length,
        totalCount: totalFiles.length,
        templateTargetObjid,
        dataTargetObjid:
          tableName && recordId && columnName
            ? `${tableName}:${recordId}:${columnName}`
            : null,
      },
    });
  } catch (error) {
    console.error("컴포넌트 파일 조회 오류:", error);
    res.status(500).json({
      success: false,
      message: "컴포넌트 파일 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "알 수 없는 오류",
    });
  }
};

/**
 * 파일 미리보기 (이미지 등)
 */
export const previewFile = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { objid } = req.params;
    const { serverFilename } = req.query;

    // 🔒 멀티테넌시: 현재 사용자의 회사 코드
    const companyCode = req.user?.companyCode;

    const fileRecord = await queryOne<any>(
      "SELECT * FROM attach_file_info WHERE objid = $1 LIMIT 1",
      [objid]
    );

    if (!fileRecord || fileRecord.status !== "ACTIVE") {
      res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
      return;
    }

    // 🔒 멀티테넌시: 회사 코드 일치 여부 확인 (최고 관리자 및 공개 접근 제외)
    // 공개 접근(req.user가 없는 경우)은 미리보기 허용 (이미지 표시용)
    if (companyCode && companyCode !== "*" && fileRecord.company_code !== companyCode) {
      console.warn("⚠️ 다른 회사 파일 접근 시도:", {
        userId: req.user?.userId,
        userCompanyCode: companyCode,
        fileCompanyCode: fileRecord.company_code,
        objid,
      });
      res.status(403).json({
        success: false,
        message: "접근 권한이 없습니다.",
      });
      return;
    }

    // 파일 경로에서 회사코드와 날짜 폴더 추출
    const filePathParts = fileRecord.file_path!.split("/");
    let fileCompanyCode = filePathParts[2] || "DEFAULT";

    // company_* 처리 (실제 회사 코드로 변환)
    if (fileCompanyCode === "company_*") {
      fileCompanyCode = "company_*"; // 실제 디렉토리명 유지
    }

    const fileName = fileRecord.saved_file_name!;

    // 파일 경로에 날짜 구조가 있는지 확인 (YYYY/MM/DD)
    let dateFolder = "";
    if (filePathParts.length >= 6) {
      dateFolder = `${filePathParts[3]}/${filePathParts[4]}/${filePathParts[5]}`;
    }

    const companyUploadDir = getCompanyUploadDir(
      fileCompanyCode,
      dateFolder || undefined
    );
    const filePath = path.join(companyUploadDir, fileName);

    console.log("🔍 파일 미리보기 경로 확인:", {
      objid: objid,
      filePathFromDB: fileRecord.file_path,
      companyCode: companyCode,
      dateFolder: dateFolder,
      fileName: fileName,
      companyUploadDir: companyUploadDir,
      finalFilePath: filePath,
      fileExists: fs.existsSync(filePath),
    });

    if (!fs.existsSync(filePath)) {
      console.error("❌ 파일 없음:", filePath);
      res.status(404).json({
        success: false,
        message: `실제 파일을 찾을 수 없습니다: ${filePath}`,
      });
      return;
    }

    // MIME 타입 설정
    const ext = path.extname(fileName).toLowerCase();
    let mimeType = "application/octet-stream";

    switch (ext) {
      case ".jpg":
      case ".jpeg":
        mimeType = "image/jpeg";
        break;
      case ".png":
        mimeType = "image/png";
        break;
      case ".gif":
        mimeType = "image/gif";
        break;
      case ".webp":
        mimeType = "image/webp";
        break;
      case ".pdf":
        mimeType = "application/pdf";
        break;
      default:
        mimeType = "application/octet-stream";
    }

    // CORS 헤더 설정 (credentials 모드에서는 구체적인 origin 필요)
    const origin = req.headers.origin || "http://localhost:9771";
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader(
      "Access-Control-Allow-Methods",
      "GET, POST, PUT, DELETE, OPTIONS"
    );
    res.setHeader(
      "Access-Control-Allow-Headers",
      "Content-Type, Authorization, X-Requested-With, Accept, Origin"
    );
    res.setHeader("Access-Control-Allow-Credentials", "true");

    // 캐시 헤더 설정
    res.setHeader("Cache-Control", "public, max-age=3600");
    res.setHeader("Content-Type", mimeType);

    // 파일 스트림으로 전송
    const fileStream = fs.createReadStream(filePath);
    fileStream.pipe(res);
  } catch (error) {
    console.error("파일 미리보기 오류:", error);
    res.status(500).json({
      success: false,
      message: "파일 미리보기 중 오류가 발생했습니다.",
    });
  }
};

/**
 * 파일 다운로드
 */
export const downloadFile = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { objid } = req.params;

    // 🔒 멀티테넌시: 현재 사용자의 회사 코드
    const companyCode = req.user?.companyCode;

    const fileRecord = await queryOne<any>(
      `SELECT * FROM attach_file_info WHERE objid = $1`,
      [objid]
    );

    if (!fileRecord || fileRecord.status !== "ACTIVE") {
      res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
      return;
    }

    // 🔒 멀티테넌시: 회사 코드 일치 여부 확인 (최고 관리자 제외)
    if (companyCode !== "*" && fileRecord.company_code !== companyCode) {
      console.warn("⚠️ 다른 회사 파일 다운로드 시도:", {
        userId: req.user?.userId,
        userCompanyCode: companyCode,
        fileCompanyCode: fileRecord.company_code,
        objid,
      });
      res.status(403).json({
        success: false,
        message: "접근 권한이 없습니다.",
      });
      return;
    }

    // 파일 경로에서 회사코드와 날짜 폴더 추출 (예: /uploads/company_*/2025/09/05/timestamp_filename.ext)
    const filePathParts = fileRecord.file_path!.split("/");
    let fileCompanyCode = filePathParts[2] || "DEFAULT"; // /uploads/company_*/2025/09/05/filename.ext에서 company_* 추출

    // company_* 처리 (실제 회사 코드로 변환)
    if (fileCompanyCode === "company_*") {
      fileCompanyCode = "company_*"; // 실제 디렉토리명 유지
    }

    const fileName = fileRecord.saved_file_name!;

    // 파일 경로에 날짜 구조가 있는지 확인 (YYYY/MM/DD)
    let dateFolder = "";
    if (filePathParts.length >= 6) {
      // /uploads/company_*/2025/09/05/filename.ext 형태
      dateFolder = `${filePathParts[3]}/${filePathParts[4]}/${filePathParts[5]}`;
    }

    const companyUploadDir = getCompanyUploadDir(
      fileCompanyCode,
      dateFolder || undefined
    );
    const filePath = path.join(companyUploadDir, fileName);

    console.log("🔍 파일 다운로드 경로 확인:", {
      objid: objid,
      filePathFromDB: fileRecord.file_path,
      companyCode: companyCode,
      dateFolder: dateFolder,
      fileName: fileName,
      companyUploadDir: companyUploadDir,
      finalFilePath: filePath,
      fileExists: fs.existsSync(filePath),
    });

    if (!fs.existsSync(filePath)) {
      console.error("❌ 파일 없음:", filePath);
      res.status(404).json({
        success: false,
        message: `실제 파일을 찾을 수 없습니다: ${filePath}`,
      });
      return;
    }

    // 파일 다운로드 헤더 설정
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="${encodeURIComponent(fileRecord.real_file_name!)}"`
    );
    res.setHeader("Content-Type", "application/octet-stream");

    // 파일 스트림 전송
    const fileStream = fs.createReadStream(filePath);
    fileStream.pipe(res);
  } catch (error) {
    console.error("파일 다운로드 오류:", error);
    res.status(500).json({
      success: false,
      message: "파일 다운로드 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "알 수 없는 오류",
    });
  }
};

/**
 * Google Docs Viewer용 임시 공개 토큰 생성
 */
export const generateTempToken = async (
  req: AuthenticatedRequest,
  res: Response
) => {
  try {
    const { objid } = req.params;

    if (!objid) {
      res.status(400).json({
        success: false,
        message: "파일 ID가 필요합니다.",
      });
      return;
    }

    // 파일 존재 확인
    const fileRecord = await queryOne<any>(
      "SELECT * FROM attach_file_info WHERE objid = $1 LIMIT 1",
      [objid]
    );

    if (!fileRecord) {
      res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
      return;
    }

    // 임시 토큰 생성 (30분 유효)
    const token = generateUUID();
    const expires = Date.now() + 30 * 60 * 1000; // 30분

    tempTokens.set(token, {
      objid: objid,
      expires: expires,
    });

    // 만료된 토큰 정리 (메모리 누수 방지)
    const now = Date.now();
    for (const [key, value] of tempTokens.entries()) {
      if (value.expires < now) {
        tempTokens.delete(key);
      }
    }

    res.json({
      success: true,
      data: {
        token: token,
        publicUrl: `${req.protocol}://${req.get("host")}/api/files/public/${token}`,
        expires: new Date(expires).toISOString(),
      },
    });
  } catch (error) {
    console.error("❌ 임시 토큰 생성 오류:", error);
    res.status(500).json({
      success: false,
      message: "임시 토큰 생성에 실패했습니다.",
    });
  }
};

/**
 * 임시 토큰으로 파일 접근 (인증 불필요)
 */
export const getFileByToken = async (req: Request, res: Response) => {
  try {
    const { token } = req.params;

    if (!token) {
      res.status(400).json({
        success: false,
        message: "토큰이 필요합니다.",
      });
      return;
    }

    // 토큰 확인
    const tokenData = tempTokens.get(token);
    if (!tokenData) {
      res.status(404).json({
        success: false,
        message: "유효하지 않은 토큰입니다.",
      });
      return;
    }

    // 토큰 만료 확인
    if (tokenData.expires < Date.now()) {
      tempTokens.delete(token);
      res.status(410).json({
        success: false,
        message: "토큰이 만료되었습니다.",
      });
      return;
    }

    // 파일 정보 조회
    const fileRecord = await queryOne<any>(
      "SELECT * FROM attach_file_info WHERE objid = $1 LIMIT 1",
      [tokenData.objid]
    );

    if (!fileRecord) {
      res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
      return;
    }

    // 파일 경로 구성
    const filePathParts = fileRecord.file_path!.split("/");
    let companyCode = filePathParts[2] || "DEFAULT";
    if (companyCode === "company_*") {
      companyCode = "company_*"; // 실제 디렉토리명 유지
    }
    const fileName = fileRecord.saved_file_name!;
    let dateFolder = "";
    if (filePathParts.length >= 6) {
      dateFolder = `${filePathParts[3]}/${filePathParts[4]}/${filePathParts[5]}`;
    }
    const companyUploadDir = getCompanyUploadDir(
      companyCode,
      dateFolder || undefined
    );
    const filePath = path.join(companyUploadDir, fileName);

    // 파일 존재 확인
    if (!fs.existsSync(filePath)) {
      res.status(404).json({
        success: false,
        message: "실제 파일을 찾을 수 없습니다.",
      });
      return;
    }

    // MIME 타입 설정
    const ext = path.extname(fileName).toLowerCase();
    let contentType = "application/octet-stream";

    const mimeTypes: { [key: string]: string } = {
      ".pdf": "application/pdf",
      ".doc": "application/msword",
      ".docx":
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      ".xls": "application/vnd.ms-excel",
      ".xlsx":
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      ".ppt": "application/vnd.ms-powerpoint",
      ".pptx":
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
      ".jpg": "image/jpeg",
      ".jpeg": "image/jpeg",
      ".png": "image/png",
      ".gif": "image/gif",
      ".txt": "text/plain",
    };

    if (mimeTypes[ext]) {
      contentType = mimeTypes[ext];
    }

    // 파일 헤더 설정
    res.setHeader("Content-Type", contentType);
    res.setHeader(
      "Content-Disposition",
      `inline; filename="${encodeURIComponent(fileRecord.real_file_name!)}"`
    );
    res.setHeader("Cache-Control", "public, max-age=300"); // 5분 캐시

    // 파일 스트림 전송
    const fileStream = fs.createReadStream(filePath);
    fileStream.pipe(res);
  } catch (error) {
    console.error("❌ 토큰 파일 접근 오류:", error);
    res.status(500).json({
      success: false,
      message: "파일 접근에 실패했습니다.",
    });
  }
};

/**
 * 대표 파일 설정
 */
export const setRepresentativeFile = async (
  req: AuthenticatedRequest,
  res: Response
): Promise<void> => {
  try {
    const { objid } = req.params;
    const companyCode = req.user?.companyCode;

    // 파일 존재 여부 및 권한 확인
    const fileRecord = await queryOne<any>(
      `SELECT * FROM attach_file_info WHERE objid = $1 AND status = $2`,
      [objid, "ACTIVE"]
    );

    if (!fileRecord) {
      res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
      return;
    }

    // 멀티테넌시: 회사 코드 확인
    if (companyCode !== "*" && fileRecord.company_code !== companyCode) {
      res.status(403).json({
        success: false,
        message: "접근 권한이 없습니다.",
      });
      return;
    }

    // 같은 target_objid의 다른 파일들의 is_representative를 false로 설정
    await query<any>(
      `UPDATE attach_file_info 
       SET is_representative = false 
       WHERE target_objid = $1 AND objid != $2`,
      [fileRecord.target_objid, objid]
    );

    // 선택한 파일을 대표 파일로 설정
    await query<any>(
      `UPDATE attach_file_info 
       SET is_representative = true 
       WHERE objid = $1`,
      [objid]
    );

    res.json({
      success: true,
      message: "대표 파일이 설정되었습니다.",
    });
  } catch (error) {
    console.error("대표 파일 설정 오류:", error);
    res.status(500).json({
      success: false,
      message: "대표 파일 설정 중 오류가 발생했습니다.",
    });
  }
};

/**
 * 파일 정보 조회 (메타데이터만, 파일 내용 없음)
 * 공개 접근 허용
 */
export const getFileInfo = async (req: Request, res: Response) => {
  try {
    const { objid } = req.params;

    if (!objid) {
      return res.status(400).json({
        success: false,
        message: "파일 ID가 필요합니다.",
      });
    }

    // 파일 정보 조회
    const fileRecord = await queryOne<any>(
      `SELECT objid, real_file_name, file_size, file_ext, file_path, regdate, is_representative
       FROM attach_file_info 
       WHERE objid = $1 AND status = 'ACTIVE'`,
      [objid]
    );

    if (!fileRecord) {
      return res.status(404).json({
        success: false,
        message: "파일을 찾을 수 없습니다.",
      });
    }

    res.json({
      success: true,
      data: {
        objid: fileRecord.objid.toString(),
        realFileName: fileRecord.real_file_name,
        fileSize: fileRecord.file_size,
        fileExt: fileRecord.file_ext,
        filePath: fileRecord.file_path,
        regdate: fileRecord.regdate,
        isRepresentative: fileRecord.is_representative,
      },
    });
  } catch (error) {
    console.error("파일 정보 조회 오류:", error);
    res.status(500).json({
      success: false,
      message: "파일 정보 조회 중 오류가 발생했습니다.",
    });
  }
};

// Multer 미들웨어 export
export const uploadMiddleware = upload.array("files", 10); // 최대 10개 파일
