// 배치관리 전용 컨트롤러 (기존 소스와 완전 분리)
// 작성일: 2024-12-24

import { Request, Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import {
  BatchManagementService,
  BatchConnectionInfo,
  BatchTableInfo,
  BatchColumnInfo,
} from "../services/batchManagementService";
import { BatchService } from "../services/batchService";
import { BatchSchedulerService } from "../services/batchSchedulerService";
import { BatchExternalDbService } from "../services/batchExternalDbService";
import { CreateBatchConfigRequest, BatchConfig } from "../types/batchTypes";
import { query } from "../database/db";

export class BatchManagementController {
  /**
   * 사용 가능한 커넥션 목록 조회 (회사별)
   */
  static async getAvailableConnections(
    req: AuthenticatedRequest,
    res: Response
  ) {
    try {
      const userCompanyCode = req.user?.companyCode;
      const result =
        await BatchManagementService.getAvailableConnections(userCompanyCode);
      if (result.success) {
        res.json(result);
      } else {
        res.status(500).json(result);
      }
    } catch (error) {
      console.error("커넥션 목록 조회 오류:", error);
      res.status(500).json({
        success: false,
        message: "커넥션 목록 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 특정 커넥션의 테이블 목록 조회 (회사별)
   */
  static async getTablesFromConnection(
    req: AuthenticatedRequest,
    res: Response
  ) {
    try {
      const { type, id } = req.params;
      const userCompanyCode = req.user?.companyCode;

      if (type !== "internal" && type !== "external") {
        return res.status(400).json({
          success: false,
          message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)",
        });
      }

      const connectionId = type === "external" ? Number(id) : undefined;
      const result = await BatchManagementService.getTablesFromConnection(
        type,
        connectionId,
        userCompanyCode
      );

      if (result.success) {
        return res.json(result);
      } else {
        return res.status(500).json(result);
      }
    } catch (error) {
      console.error("테이블 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "테이블 목록 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 특정 테이블의 컬럼 정보 조회 (회사별)
   */
  static async getTableColumns(req: AuthenticatedRequest, res: Response) {
    try {
      const { type, id, tableName } = req.params;
      const userCompanyCode = req.user?.companyCode;

      if (type !== "internal" && type !== "external") {
        return res.status(400).json({
          success: false,
          message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)",
        });
      }

      const connectionId = type === "external" ? Number(id) : undefined;
      const result = await BatchManagementService.getTableColumns(
        type,
        connectionId,
        tableName,
        userCompanyCode
      );

      if (result.success) {
        return res.json(result);
      } else {
        return res.status(500).json(result);
      }
    } catch (error) {
      console.error("컬럼 정보 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "컬럼 정보 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 배치 설정 생성
   * POST /api/batch-management/batch-configs
   */
  static async createBatchConfig(req: AuthenticatedRequest, res: Response) {
    try {
      const {
        batchName, description, cronSchedule, mappings, isActive,
        executionType, nodeFlowId, nodeFlowContext,
      } = req.body;
      const companyCode = req.user?.companyCode;

      if (!batchName || !cronSchedule) {
        return res.status(400).json({
          success: false,
          message: "필수 필드가 누락되었습니다. (batchName, cronSchedule)",
        });
      }

      // 노드 플로우 타입은 매핑 없이 생성 가능
      if (executionType !== "node_flow" && (!mappings || !Array.isArray(mappings))) {
        return res.status(400).json({
          success: false,
          message: "매핑 타입은 mappings 배열이 필요합니다.",
        });
      }

      const batchConfig = await BatchService.createBatchConfig(
        {
          batchName,
          description,
          cronSchedule,
          mappings: mappings || [],
          isActive: isActive === false || isActive === "N" ? "N" : "Y",
          companyCode: companyCode || "",
          executionType: executionType || "mapping",
          nodeFlowId: nodeFlowId || null,
          nodeFlowContext: nodeFlowContext || null,
        } as CreateBatchConfigRequest,
        req.user?.userId
      );

      return res.status(201).json({
        success: true,
        data: batchConfig,
        message: "배치 설정이 성공적으로 생성되었습니다.",
      });
    } catch (error) {
      console.error("배치 설정 생성 오류:", error);
      return res.status(500).json({
        success: false,
        message: "배치 설정 생성에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 특정 배치 설정 조회
   * GET /api/batch-management/batch-configs/:id
   */
  static async getBatchConfigById(req: AuthenticatedRequest, res: Response) {
    try {
      const { id } = req.params;
      console.log("🔍 배치 설정 조회 요청:", id);

      const result = await BatchService.getBatchConfigById(Number(id));

      if (!result.success) {
        return res.status(404).json({
          success: false,
          message: result.message || "배치 설정을 찾을 수 없습니다.",
        });
      }

      console.log("📋 조회된 배치 설정:", result.data);

      return res.json({
        success: true,
        data: result.data,
      });
    } catch (error) {
      console.error("❌ 배치 설정 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "배치 설정 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 배치 설정 목록 조회
   * GET /api/batch-management/batch-configs
   */
  static async getBatchConfigs(req: AuthenticatedRequest, res: Response) {
    try {
      const { page = 1, limit = 10, search, isActive } = req.query;

      const filter = {
        page: Number(page),
        limit: Number(limit),
        search: search as string,
        is_active: isActive as string,
      };

      const result = await BatchService.getBatchConfigs(filter);

      res.json({
        success: true,
        data: result.data,
        pagination: result.pagination,
      });
    } catch (error) {
      console.error("배치 설정 목록 조회 오류:", error);
      res.status(500).json({
        success: false,
        message: "배치 설정 목록 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 배치 수동 실행
   * POST /api/batch-management/batch-configs/:id/execute
   */
  static async executeBatchConfig(req: AuthenticatedRequest, res: Response) {
    try {
      const { id } = req.params;

      if (!id || isNaN(Number(id))) {
        return res.status(400).json({
          success: false,
          message: "올바른 배치 설정 ID를 제공해주세요.",
        });
      }

      // 배치 설정 조회
      const batchConfigResult = await BatchService.getBatchConfigById(
        Number(id)
      );
      if (!batchConfigResult.success || !batchConfigResult.data) {
        return res.status(404).json({
          success: false,
          message: "배치 설정을 찾을 수 없습니다.",
        });
      }

      const batchConfig = batchConfigResult.data as BatchConfig;
      const startTime = new Date();

      console.log(`배치 수동 실행 시작: ${batchConfig.batch_name} (ID: ${id})`);

      let executionLog: any = null;

      try {
        // 실행 로그 생성
        const { BatchExecutionLogService } = await import(
          "../services/batchExecutionLogService"
        );
        const logResult = await BatchExecutionLogService.createExecutionLog({
          batch_config_id: Number(id),
          company_code: batchConfig.company_code,
          execution_status: "RUNNING",
          start_time: startTime,
          total_records: 0,
          success_records: 0,
          failed_records: 0,
        });

        if (!logResult.success || !logResult.data) {
          throw new Error(
            logResult.message || "배치 실행 로그를 생성할 수 없습니다."
          );
        }

        executionLog = logResult.data;

        // BatchSchedulerService의 executeBatchConfig 메서드 사용 (중복 로직 제거)
        const { BatchSchedulerService } = await import(
          "../services/batchSchedulerService"
        );
        const result =
          await BatchSchedulerService.executeBatchConfig(batchConfig);

        // result가 undefined인 경우 처리
        if (!result) {
          throw new Error("배치 실행 결과를 받을 수 없습니다.");
        }

        const endTime = new Date();
        const duration = endTime.getTime() - startTime.getTime();

        // 실행 로그 업데이트 (성공)
        await BatchExecutionLogService.updateExecutionLog(executionLog.id, {
          execution_status: "SUCCESS",
          end_time: endTime,
          duration_ms: duration,
          total_records: result.totalRecords,
          success_records: result.successRecords,
          failed_records: result.failedRecords,
        });

        return res.json({
          success: true,
          data: {
            batchName: batchConfig.batch_name,
            totalRecords: result.totalRecords,
            successRecords: result.successRecords,
            failedRecords: result.failedRecords,
            executionTime: duration,
          },
          message: "배치가 성공적으로 실행되었습니다.",
        });
      } catch (batchError) {
        console.error(`배치 실행 실패: ${batchConfig.batch_name}`, batchError);

        // 실행 로그 업데이트 (실패) - executionLog가 생성되었을 경우에만
        try {
          const endTime = new Date();
          const duration = endTime.getTime() - startTime.getTime();

          // executionLog가 정의되어 있는지 확인
          if (typeof executionLog !== "undefined" && executionLog) {
            const { BatchExecutionLogService } = await import(
              "../services/batchExecutionLogService"
            );
            await BatchExecutionLogService.updateExecutionLog(executionLog.id, {
              execution_status: "FAILED",
              end_time: endTime,
              duration_ms: duration,
              error_message:
                batchError instanceof Error
                  ? batchError.message
                  : "알 수 없는 오류",
            });
          }
        } catch (logError) {
          console.error("실행 로그 업데이트 실패:", logError);
        }

        return res.status(500).json({
          success: false,
          message: "배치 실행에 실패했습니다.",
          error:
            batchError instanceof Error
              ? batchError.message
              : "알 수 없는 오류",
        });
      }
    } catch (error) {
      console.error(`배치 실행 오류 (ID: ${req.params.id}):`, error);
      return res.status(500).json({
        success: false,
        message: "배치 실행 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      });
    }
  }

  /**
   * 배치 설정 업데이트
   * PUT /api/batch-management/batch-configs/:id
   */
  static async updateBatchConfig(req: AuthenticatedRequest, res: Response) {
    try {
      const { id } = req.params;
      const updateData = req.body;

      if (!id || isNaN(Number(id))) {
        return res.status(400).json({
          success: false,
          message: "올바른 배치 설정 ID를 제공해주세요.",
        });
      }

      const batchConfig = await BatchService.updateBatchConfig(
        Number(id),
        updateData
      );

      // 스케줄러에서 배치 스케줄 업데이트 (즉시 실행 비활성화)
      await BatchSchedulerService.updateBatchSchedule(Number(id), false);

      return res.json({
        success: true,
        data: batchConfig,
        message: "배치 설정이 성공적으로 업데이트되었습니다.",
      });
    } catch (error) {
      console.error("배치 설정 업데이트 오류:", error);
      return res.status(500).json({
        success: false,
        message: "배치 설정 업데이트에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * REST API 데이터 미리보기
   */
  static async previewRestApiData(req: AuthenticatedRequest, res: Response) {
    try {
      const {
        apiUrl,
        apiKey,
        endpoint,
        method = "GET",
        paramType,
        paramName,
        paramValue,
        paramSource,
        requestBody,
        authServiceName, // DB에서 토큰 가져올 서비스명
        dataArrayPath, // 데이터 배열 경로 (예: response, data.items)
      } = req.body;

      // apiUrl, endpoint는 항상 필수
      if (!apiUrl || !endpoint) {
        return res.status(400).json({
          success: false,
          message: "API URL과 엔드포인트는 필수입니다.",
        });
      }

      // 토큰 결정: authServiceName이 있으면 DB에서 조회, 없으면 apiKey 사용
      let finalApiKey = apiKey || "";
      if (authServiceName) {
        const companyCode = req.user?.companyCode;

        // DB에서 토큰 조회 (멀티테넌시: company_code 필터링)
        let tokenQuery: string;
        let tokenParams: any[];

        if (companyCode === "*") {
          // 최고 관리자: 모든 회사 토큰 조회 가능
          tokenQuery = `SELECT access_token FROM auth_tokens 
                        WHERE service_name = $1 
                        ORDER BY created_date DESC LIMIT 1`;
          tokenParams = [authServiceName];
        } else {
          // 일반 회사: 자신의 회사 토큰만 조회
          tokenQuery = `SELECT access_token FROM auth_tokens 
                        WHERE service_name = $1 AND company_code = $2
                        ORDER BY created_date DESC LIMIT 1`;
          tokenParams = [authServiceName, companyCode];
        }

        const tokenResult = await query<{ access_token: string }>(
          tokenQuery,
          tokenParams
        );
        if (tokenResult.length > 0 && tokenResult[0].access_token) {
          finalApiKey = tokenResult[0].access_token;
          console.log(`auth_tokens에서 토큰 조회 성공: ${authServiceName}`);
        } else {
          return res.status(400).json({
            success: false,
            message: `서비스 '${authServiceName}'의 토큰을 찾을 수 없습니다. 먼저 토큰 저장 배치를 실행하세요.`,
          });
        }
      }

      // 토큰이 없어도 공개 API 호출 가능 (토큰 검증 제거)

      console.log("REST API 미리보기 요청:", {
        apiUrl,
        endpoint,
        method,
        paramType,
        paramName,
        paramValue,
        paramSource,
        requestBody: requestBody ? "Included" : "None",
        authServiceName: authServiceName || "직접 입력",
        dataArrayPath: dataArrayPath || "전체 응답",
      });

      // RestApiConnector 사용하여 데이터 조회
      const { RestApiConnector } = await import("../database/RestApiConnector");

      const connector = new RestApiConnector({
        baseUrl: apiUrl,
        apiKey: finalApiKey,
        timeout: 30000,
      });

      // 연결 테스트
      await connector.connect();

      // 파라미터가 있는 경우 엔드포인트 수정
      let finalEndpoint = endpoint;
      if (paramType && paramName && paramValue) {
        if (paramType === "url") {
          // URL 파라미터: /api/users/{userId} → /api/users/123
          if (endpoint.includes(`{${paramName}}`)) {
            finalEndpoint = endpoint.replace(`{${paramName}}`, paramValue);
          } else {
            // 엔드포인트에 {paramName}이 없으면 뒤에 추가
            finalEndpoint = `${endpoint}/${paramValue}`;
          }
        } else if (paramType === "query") {
          // 쿼리 파라미터: /api/users?userId=123
          const separator = endpoint.includes("?") ? "&" : "?";
          finalEndpoint = `${endpoint}${separator}${paramName}=${paramValue}`;
        }
      }

      console.log("🔗 최종 엔드포인트:", finalEndpoint);

      // Request Body 파싱
      let parsedBody = undefined;
      if (requestBody && typeof requestBody === "string") {
        try {
          parsedBody = JSON.parse(requestBody);
        } catch (e) {
          console.warn("Request Body JSON 파싱 실패:", e);
          // 파싱 실패 시 원본 문자열 사용하거나 무시 (상황에 따라 결정, 여기선 undefined로 처리하거나 에러 반환 가능)
          // 여기서는 경고 로그 남기고 진행
        }
      } else if (requestBody) {
        parsedBody = requestBody;
      }

      // 데이터 조회 - executeRequest 사용 (POST/PUT/DELETE 지원)
      const result = await connector.executeRequest(
        finalEndpoint,
        method as "GET" | "POST" | "PUT" | "DELETE",
        parsedBody
      );

      console.log(`[previewRestApiData] executeRequest 결과:`, {
        rowCount: result.rowCount,
        rowsLength: result.rows ? result.rows.length : "undefined",
        firstRow:
          result.rows && result.rows.length > 0 ? result.rows[0] : "no data",
      });

      // 데이터 배열 추출 헬퍼 함수
      const getValueByPath = (obj: any, path: string): any => {
        if (!path) return obj;
        const keys = path.split(".");
        let current = obj;
        for (const key of keys) {
          if (current === null || current === undefined) return undefined;
          current = current[key];
        }
        return current;
      };

      // dataArrayPath가 있으면 해당 경로에서 배열 추출
      let extractedData: any[] = [];
      if (dataArrayPath) {
        // result.rows가 단일 객체일 수 있음 (API 응답 전체)
        const rawData = result.rows.length === 1 ? result.rows[0] : result.rows;
        const arrayData = getValueByPath(rawData, dataArrayPath);

        if (Array.isArray(arrayData)) {
          extractedData = arrayData;
          console.log(
            `[previewRestApiData] '${dataArrayPath}' 경로에서 ${arrayData.length}개 항목 추출`
          );
        } else {
          console.warn(
            `[previewRestApiData] '${dataArrayPath}' 경로가 배열이 아님:`,
            typeof arrayData
          );
          // 배열이 아니면 단일 객체로 처리
          if (arrayData) {
            extractedData = [arrayData];
          }
        }
      } else {
        // dataArrayPath가 없으면 기존 로직 사용
        extractedData = result.rows;
      }

      const data = extractedData.slice(0, 5); // 최대 5개 샘플만
      console.log(
        `[previewRestApiData] 슬라이스된 데이터 (${extractedData.length}개 중 ${data.length}개):`,
        data
      );

      if (data.length > 0) {
        // 첫 번째 객체에서 필드명 추출
        const fields = Object.keys(data[0]);
        console.log(`[previewRestApiData] 추출된 필드:`, fields);

        return res.json({
          success: true,
          data: {
            fields: fields,
            samples: data,
            totalCount: extractedData.length,
          },
          message: `${fields.length}개 필드, ${extractedData.length}개 레코드를 조회했습니다.`,
        });
      } else {
        return res.json({
          success: true,
          data: {
            fields: [],
            samples: [],
            totalCount: 0,
          },
          message: "API에서 데이터를 가져올 수 없습니다.",
        });
      }
    } catch (error) {
      console.error("REST API 미리보기 오류:", error);
      return res.status(500).json({
        success: false,
        message: "REST API 데이터 미리보기 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * REST API 배치 설정 저장
   */
  static async saveRestApiBatch(req: AuthenticatedRequest, res: Response) {
    try {
      const {
        batchName,
        batchType,
        cronSchedule,
        description,
        apiMappings,
        authServiceName,
        dataArrayPath,
        saveMode,
        conflictKey,
      } = req.body;

      if (
        !batchName ||
        !batchType ||
        !cronSchedule ||
        !apiMappings ||
        apiMappings.length === 0
      ) {
        return res.status(400).json({
          success: false,
          message: "필수 필드가 누락되었습니다.",
        });
      }

      console.log("REST API 배치 저장 요청:", {
        batchName,
        batchType,
        cronSchedule,
        description,
        apiMappings,
        authServiceName,
        dataArrayPath,
        saveMode,
        conflictKey,
      });

      // 🔐 멀티테넌시: 현재 사용자 회사 코드 사용 (프론트에서 받지 않음)
      const companyCode = req.user?.companyCode || "*";
      const userId = req.user?.userId;

      // BatchService를 사용하여 배치 설정 저장
      const batchConfig: CreateBatchConfigRequest = {
        batchName: batchName,
        description: description || "",
        cronSchedule: cronSchedule,
        isActive: "Y",
        companyCode,
        authServiceName: authServiceName || undefined,
        dataArrayPath: dataArrayPath || undefined,
        saveMode: saveMode || "INSERT",
        conflictKey: conflictKey || undefined,
        mappings: apiMappings,
      };

      const result = await BatchService.createBatchConfig(batchConfig, userId);

      if (result.success && result.data) {
        // 스케줄러에 자동 등록 ✅
        try {
          await BatchSchedulerService.scheduleBatch(result.data);
          console.log(
            `✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})`
          );
        } catch (schedulerError) {
          console.error(`❌ 스케줄러 등록 실패: ${batchName}`, schedulerError);
          // 스케줄러 등록 실패해도 배치 저장은 성공으로 처리
        }

        return res.json({
          success: true,
          message: "REST API 배치가 성공적으로 저장되었습니다.",
          data: result.data,
        });
      } else {
        return res.status(500).json({
          success: false,
          message: result.message || "배치 저장에 실패했습니다.",
        });
      }
    } catch (error) {
      console.error("REST API 배치 저장 오류:", error);
      return res.status(500).json({
        success: false,
        message: "배치 저장 중 오류가 발생했습니다.",
      });
    }
  }

  /**
   * 인증 토큰 서비스명 목록 조회
   */
  static async getAuthServiceNames(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;

      // 멀티테넌시: company_code 필터링
      let queryText: string;
      let queryParams: any[] = [];

      if (companyCode === "*") {
        // 최고 관리자: 모든 서비스 조회
        queryText = `SELECT DISTINCT service_name 
                     FROM auth_tokens 
                     WHERE service_name IS NOT NULL 
                     ORDER BY service_name`;
      } else {
        // 일반 회사: 자신의 회사 서비스만 조회
        queryText = `SELECT DISTINCT service_name 
                     FROM auth_tokens 
                     WHERE service_name IS NOT NULL 
                       AND company_code = $1
                     ORDER BY service_name`;
        queryParams = [companyCode];
      }

      const result = await query<{ service_name: string }>(
        queryText,
        queryParams
      );

      const serviceNames = result.map((row) => row.service_name);

      return res.json({
        success: true,
        data: serviceNames,
      });
    } catch (error) {
      console.error("인증 서비스 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "인증 서비스 목록 조회 중 오류가 발생했습니다.",
      });
    }
  }

  /**
   * 노드 플로우 목록 조회 (배치 설정에서 플로우 선택용)
   * GET /api/batch-management/node-flows
   */
  static async getNodeFlows(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;

      let flowQuery: string;
      let flowParams: any[] = [];

      if (companyCode === "*") {
        flowQuery = `
          SELECT flow_id, flow_name, flow_description AS description, company_code,
                 COALESCE(jsonb_array_length(
                   CASE WHEN flow_data IS NOT NULL AND flow_data::text != ''
                        THEN (flow_data::jsonb -> 'nodes')
                        ELSE '[]'::jsonb END
                 ), 0) AS node_count
          FROM node_flows
          ORDER BY flow_name
        `;
      } else {
        flowQuery = `
          SELECT flow_id, flow_name, flow_description AS description, company_code,
                 COALESCE(jsonb_array_length(
                   CASE WHEN flow_data IS NOT NULL AND flow_data::text != ''
                        THEN (flow_data::jsonb -> 'nodes')
                        ELSE '[]'::jsonb END
                 ), 0) AS node_count
          FROM node_flows
          WHERE company_code = $1
          ORDER BY flow_name
        `;
        flowParams = [companyCode];
      }

      const result = await query(flowQuery, flowParams);
      return res.json({ success: true, data: result });
    } catch (error) {
      console.error("노드 플로우 목록 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "노드 플로우 목록 조회 실패",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 배치 대시보드 통계 조회
   * GET /api/batch-management/stats
   * totalBatches, activeBatches, todayExecutions, todayFailures, prevDayExecutions, prevDayFailures
   * 멀티테넌시: company_code 필터링 필수
   */
  static async getBatchStats(req: AuthenticatedRequest, res: Response) {
    try {
      const companyCode = req.user?.companyCode;

      // 전체/활성 배치 수
      let configQuery: string;
      let configParams: any[] = [];
      if (companyCode === "*") {
        configQuery = `
          SELECT
            COUNT(*)::int AS total,
            COUNT(*) FILTER (WHERE is_active = 'Y')::int AS active
          FROM batch_configs
        `;
      } else {
        configQuery = `
          SELECT
            COUNT(*)::int AS total,
            COUNT(*) FILTER (WHERE is_active = 'Y')::int AS active
          FROM batch_configs
          WHERE company_code = $1
        `;
        configParams = [companyCode];
      }
      const configResult = await query<{ total: number; active: number }>(
        configQuery,
        configParams
      );

      // 오늘/어제 실행·실패 수 (KST 기준 날짜)
      const logParams: any[] = [];
      let logWhere = "";
      if (companyCode && companyCode !== "*") {
        logWhere = " AND company_code = $1";
        logParams.push(companyCode);
      }
      const todayLogQuery = `
        SELECT
          COUNT(*)::int AS today_executions,
          COUNT(*) FILTER (WHERE execution_status = 'FAILED')::int AS today_failures
        FROM batch_execution_logs
        WHERE (start_time AT TIME ZONE 'Asia/Seoul')::date = (NOW() AT TIME ZONE 'Asia/Seoul')::date
        ${logWhere}
      `;
      const prevDayLogQuery = `
        SELECT
          COUNT(*)::int AS prev_executions,
          COUNT(*) FILTER (WHERE execution_status = 'FAILED')::int AS prev_failures
        FROM batch_execution_logs
        WHERE (start_time AT TIME ZONE 'Asia/Seoul')::date = (NOW() AT TIME ZONE 'Asia/Seoul')::date - INTERVAL '1 day'
        ${logWhere}
      `;
      const [todayResult, prevResult] = await Promise.all([
        query<{ today_executions: number; today_failures: number }>(
          todayLogQuery,
          logParams
        ),
        query<{ prev_executions: number; prev_failures: number }>(
          prevDayLogQuery,
          logParams
        ),
      ]);

      const config = configResult[0];
      const today = todayResult[0];
      const prev = prevResult[0];

      return res.json({
        success: true,
        data: {
          totalBatches: config?.total ?? 0,
          activeBatches: config?.active ?? 0,
          todayExecutions: today?.today_executions ?? 0,
          todayFailures: today?.today_failures ?? 0,
          prevDayExecutions: prev?.prev_executions ?? 0,
          prevDayFailures: prev?.prev_failures ?? 0,
        },
      });
    } catch (error) {
      console.error("배치 통계 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "배치 통계 조회 실패",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 배치별 최근 24시간 스파크라인 (1시간 단위 집계)
   * GET /api/batch-management/batch-configs/:id/sparkline
   * 멀티테넌시: company_code 필터링 필수
   */
  static async getBatchSparkline(req: AuthenticatedRequest, res: Response) {
    try {
      const { id } = req.params;
      const companyCode = req.user?.companyCode;
      const batchId = Number(id);
      if (!id || isNaN(batchId)) {
        return res.status(400).json({
          success: false,
          message: "올바른 배치 ID를 제공해주세요.",
        });
      }

      const params: any[] = [batchId];
      let companyFilter = "";
      if (companyCode && companyCode !== "*") {
        companyFilter = " AND bel.company_code = $2";
        params.push(companyCode);
      }

      // KST 기준 최근 24시간 1시간 단위 슬롯 + 집계 (generate_series로 24개 보장)
      const sparklineQuery = `
        WITH kst_slots AS (
          SELECT to_char(s, 'YYYY-MM-DD"T"HH24:00:00') AS hour
          FROM generate_series(
            (NOW() AT TIME ZONE 'Asia/Seoul') - INTERVAL '23 hours',
            (NOW() AT TIME ZONE 'Asia/Seoul'),
            INTERVAL '1 hour'
          ) AS s
        ),
        agg AS (
          SELECT
            to_char(date_trunc('hour', (bel.start_time AT TIME ZONE 'Asia/Seoul')) AT TIME ZONE 'Asia/Seoul', 'YYYY-MM-DD"T"HH24:00:00') AS hour,
            COUNT(*) FILTER (WHERE bel.execution_status = 'SUCCESS')::int AS success,
            COUNT(*) FILTER (WHERE bel.execution_status = 'FAILED')::int AS failed
          FROM batch_execution_logs bel
          WHERE bel.batch_config_id = $1
            AND bel.start_time >= (NOW() AT TIME ZONE 'Asia/Seoul') - INTERVAL '24 hours'
            ${companyFilter}
          GROUP BY date_trunc('hour', (bel.start_time AT TIME ZONE 'Asia/Seoul'))
        )
        SELECT
          k.hour,
          COALESCE(a.success, 0) AS success,
          COALESCE(a.failed, 0) AS failed
        FROM kst_slots k
        LEFT JOIN agg a ON k.hour = a.hour
        ORDER BY k.hour
      `;
      const data = await query<{
        hour: string;
        success: number;
        failed: number;
      }>(sparklineQuery, params);

      return res.json({ success: true, data });
    } catch (error) {
      console.error("스파크라인 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "스파크라인 데이터 조회 실패",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }

  /**
   * 배치별 최근 실행 로그 (최대 20건)
   * GET /api/batch-management/batch-configs/:id/recent-logs
   * 멀티테넌시: company_code 필터링 필수
   */
  static async getBatchRecentLogs(req: AuthenticatedRequest, res: Response) {
    try {
      const { id } = req.params;
      const companyCode = req.user?.companyCode;
      const batchId = Number(id);
      const limit = Math.min(Number(req.query.limit) || 20, 20);
      if (!id || isNaN(batchId)) {
        return res.status(400).json({
          success: false,
          message: "올바른 배치 ID를 제공해주세요.",
        });
      }

      let logsQuery: string;
      let logsParams: any[];
      if (companyCode === "*") {
        logsQuery = `
          SELECT
            id,
            start_time AS started_at,
            end_time AS finished_at,
            execution_status AS status,
            total_records,
            success_records,
            failed_records,
            error_message,
            duration_ms
          FROM batch_execution_logs
          WHERE batch_config_id = $1
          ORDER BY start_time DESC
          LIMIT $2
        `;
        logsParams = [batchId, limit];
      } else {
        logsQuery = `
          SELECT
            id,
            start_time AS started_at,
            end_time AS finished_at,
            execution_status AS status,
            total_records,
            success_records,
            failed_records,
            error_message,
            duration_ms
          FROM batch_execution_logs
          WHERE batch_config_id = $1 AND company_code = $2
          ORDER BY start_time DESC
          LIMIT $3
        `;
        logsParams = [batchId, companyCode, limit];
      }

      const result = await query(logsQuery, logsParams);
      return res.json({ success: true, data: result });
    } catch (error) {
      console.error("최근 실행 이력 조회 오류:", error);
      return res.status(500).json({
        success: false,
        message: "최근 실행 이력 조회 실패",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      });
    }
  }
}
