/**
 * CP 조립 공정 모니터링 데이터 변환 유틸리티
 * - transformProcColumns: Realtime/Daily용 (PROC_* 컬럼 형태)
 * - transformProcessStatus: 공정 상태용 (구조화된 객체 형태)
 */

import { PinoLogger } from 'nestjs-pino';
import {
  PROC_MAP,
  ProcMapEntry,
} from 'src/api/monitoring/cp/assembly/constants/proc-map.const';
import { convertGubunToStatus } from 'src/api/monitoring/cp/assembly/constants/process-status.const';
import { matchKeyword } from 'src/common/utils/string-match.util';
import {
  AssemblyMonitoringParsedProcDto,
  AssemblyMonitoringProcRowsByProcessDto,
} from 'src/api/monitoring/cp/assembly/dto/internal/proc.transform.dto';
import { AssemblyMonitoringProcColumnRowDbDto } from 'src/api/monitoring/cp/assembly/dto/internal/proc-column-row.db.dto';
import { AssemblyMonitoringUnitProcessStatusDbDto } from 'src/api/monitoring/cp/assembly/dto/internal/unit-process-status.db.dto';

type ProcColumnRow = AssemblyMonitoringProcColumnRowDbDto;

type ProcessStatusRow = Partial<
  Pick<
    AssemblyMonitoringUnitProcessStatusDbDto,
    | 'ProcTypeName'
    | 'ModelSerialList'
    | 'StartDT'
    | 'EndDT'
    | 'WorkerCount'
    | 'TotalWorkHour'
    | 'Gubun'
    | 'WaitingDays'
  >
> & {
  StartDT?: string | Date | null;
  EndDT?: string | Date | null;
  Gubun?: string | number | null;
};

class ProcTransformer {}

const logger = new PinoLogger({
  pinoHttp: {
    level: process.env.LOG_LEVEL || 'info',
  },
});
logger.setContext(ProcTransformer.name);

function initEmptyResult(): { merged: AssemblyMonitoringProcRowsByProcessDto } {
  const merged = PROC_MAP.reduce((acc, entry) => {
    acc[entry.processName] = [];
    return acc;
  }, {} as AssemblyMonitoringProcRowsByProcessDto);

  return { merged };
}

/**
 * Date를 "2026-01-08 13:00" 형식으로 변환
 */
function formatDateTime(date: Date | string | null | undefined): string {
  if (!date) return '';

  const d = new Date(date);
  if (isNaN(d.getTime())) return '';

  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  const hour = String(d.getHours()).padStart(2, '0');
  const minute = String(d.getMinutes()).padStart(2, '0');

  return `${year}-${month}-${day} ${hour}:${minute}`;
}

/**
 * 공정명을 PROC_MAP의 entry와 매칭
 *
 * @param processName - DB에서 온 공정명 (예: "CP_BP_코어펌프조립")
 * @returns 매칭된 ProcMapEntry 또는 null
 */
export function findProcMapEntry(processName: string): ProcMapEntry | null {
  if (!processName) return null;

  for (const entry of PROC_MAP) {
    for (const keyword of entry.keywords) {
      if (matchKeyword(processName, keyword)) {
        return entry;
      }
    }
  }

  return null;
}

/**
 * 단일 PROC 문자열 파싱 (Realtime/Daily용)
 *
 * @param procString - "LP690 / 0006\nCP_BP_코어펌프조립\n시작시간: 2026-01-05 08:30\n작업인원: 2\n투입공수: 0.0\n작업자: 장문영,정경환"
 * @returns AssemblyMonitoringParsedProcDto 객체 또는 null
 */
function parseProcString(
  procString: string | null,
): AssemblyMonitoringParsedProcDto | null {
  if (!procString || procString === null) {
    return null;
  }

  // 숫자만 있는 경우 파싱하지 않음 (PROC_7 같은 경우)
  if (/^\d+$/.test(procString.trim())) {
    return null;
  }

  try {
    const lines = procString.split('\n').map((line) => line.trim());

    // 첫 번째 줄: "LP690 / 0006" 형식
    const [modelPart, serialNoPart] = lines[0].split('/').map((s) => s.trim());

    // 두 번째 줄: 공정명
    const processName = lines[1] || '';

    // 나머지 줄들에서 정보 추출
    let startTime = '';
    let workerCount = 0;
    let inputHours = 0;
    let workers: string[] = [];

    for (let i = 2; i < lines.length; i++) {
      const line = lines[i];

      if (line.startsWith('시작시간:')) {
        startTime = line.replace('시작시간:', '').trim();
      } else if (line.startsWith('작업인원:')) {
        workerCount = parseInt(line.replace('작업인원:', '').trim(), 10) || 0;
      } else if (line.startsWith('투입공수:')) {
        inputHours = parseFloat(line.replace('투입공수:', '').trim()) || 0;
      } else if (line.startsWith('작업자:')) {
        const workerStr = line.replace('작업자:', '').trim();
        workers = workerStr ? workerStr.split(',').map((w) => w.trim()) : [];
      }
    }

    return {
      model: modelPart || '',
      serialNo: serialNoPart || '',
      processName,
      startTime,
      workerCount,
      inputHours,
      workers,
    };
  } catch (error) {
    const err = error instanceof Error ? error : undefined;
    logger.error(
      {
        event: 'svc.monitoring.parse_error',
        operation: 'proc_string',
        procString,
        err,
      },
      'parseProcString failed',
    );
    return null;
  }
}

/**
 * Realtime/Daily 데이터 변환
 * PROC_* 컬럼 형태의 데이터를 공정별로 그룹핑
 *
 * @param rows - [{ PROC_1: "...", PROC_2: "...", ... }]
 */
export function transformProcColumns(
  rows: ProcColumnRow[],
): AssemblyMonitoringProcRowsByProcessDto {
  const { merged } = initEmptyResult();

  for (const row of rows) {
    for (const [key, value] of Object.entries(row)) {
      if (/^PROC_\d+$/.test(key)) {
        const parsed = parseProcString(value as string);

        if (parsed) {
          const matched = findProcMapEntry(parsed.processName);

          if (matched) {
            merged[matched.processName].push(parsed);
          }
        }
      }
    }
  }

  return merged;
}

/**
 * Monthly 데이터 변환
 * 구조화된 객체 형태의 데이터를 공정별로 그룹핑
 *
 * @param rows - [{ ProcTypeName, ModelSerialList, StartDT, WorkerCount, TotalWorkHour, ... }]
 */
export function transformProcessStatus(
  rows: ProcessStatusRow[],
  statusMapper: (
    gubun: string | number | null | undefined,
  ) => string = convertGubunToStatus,
): AssemblyMonitoringProcRowsByProcessDto {
  const { merged } = initEmptyResult();

  for (const row of rows) {
    const procTypeName = row.ProcTypeName as string;
    if (!procTypeName) continue;

    const matched = findProcMapEntry(procTypeName);

    if (!matched) continue;

    // ModelSerialList 파싱: "CP42RZ1 / 1038" 형식
    const modelSerialList = (row.ModelSerialList as string) || '';
    const [model, serialNo] = modelSerialList.split('/').map((s) => s.trim());

    // 날짜를 "2026-01-08 13:00" 형식으로 변환
    const startTime = formatDateTime(row.StartDT);
    const endTime = row.EndDT ? formatDateTime(row.EndDT) : null;

    const parsed: AssemblyMonitoringParsedProcDto = {
      model: model || '',
      serialNo: serialNo || '',
      processName: procTypeName,
      startTime,
      workerCount: row.WorkerCount ?? 0,
      inputHours: row.TotalWorkHour ?? 0,
      workers: [], // process status 데이터에는 작업자 정보 없음
      status: statusMapper(row.Gubun),
      endTime,
      waitingDays: row.WaitingDays ?? null,
    };

    merged[matched.processName].push(parsed);
  }

  return merged;
}
