/**
 * SmartExcelUpload — 템플릿 파서
 * 업로드된 엑셀 파일 파싱 + 해시 검증 + 데이터 검증
 */

import ExcelJS from "exceljs";
import type {
  SmartExcelUploadConfig,
  ParseResult,
  ParsedSheetData,
  ValidationError,
  ConditionalRule,
  ItemProcessMapping,
} from "./types";
import { regenerateHash } from "./templateGenerator";

export interface ParseOptions {
  config: SmartExcelUploadConfig;
  file: File;
  /** 현재 DB의 참조 데이터 (해시 검증용) */
  currentReferenceData: Record<string, any>[];
  /** 현재 DB의 드롭다운 옵션 (해시 검증용) */
  currentDropdownOptions: Record<string, string[]>;
  /** 현재 DB의 품목공정 매핑 (해시 검증용) */
  currentItemProcessMappings?: ItemProcessMapping[];
  /** 참조 데이터 매핑 (라벨→코드 변환 등) */
  labelToCodeMap?: Record<string, Record<string, string>>;
}

/** 셀 값을 문자열로 안전 변환 */
function cellToString(cell: ExcelJS.Cell): string {
  const val = cell.value;
  if (val === null || val === undefined) return "";

  // 수식 객체
  if (typeof val === "object" && "formula" in val) {
    if ("result" in val) {
      return String((val as any).result ?? "");
    }
    // 수식만 있고 결과 없음 (미계산) → 빈 문자열
    return "";
  }
  // RichText
  if (typeof val === "object" && "richText" in val) {
    return (val as any).richText.map((r: any) => r.text).join("");
  }

  return String(val).trim();
}

/** _meta 시트에서 메타 정보 읽기 */
function readMeta(
  workbook: ExcelJS.Workbook
): Record<string, string> | null {
  const metaSheet = workbook.getWorksheet("_meta");
  if (!metaSheet) return null;

  const meta: Record<string, string> = {};
  metaSheet.eachRow((row) => {
    const key = cellToString(row.getCell(1));
    const value = cellToString(row.getCell(2));
    if (key) meta[key] = value;
  });
  return meta;
}

/** 엑셀 파일 파싱 + 검증 */
export async function parseTemplate(
  options: ParseOptions
): Promise<ParseResult> {
  const {
    config,
    file,
    currentReferenceData,
    currentDropdownOptions,
    currentItemProcessMappings,
    labelToCodeMap,
  } = options;

  const errors: ValidationError[] = [];
  const warnings: string[] = [];
  const data: ParsedSheetData[] = [];

  // 파일 읽기
  const arrayBuffer = await file.arrayBuffer();
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.load(arrayBuffer);

  // ═══════════════════ 1. 메타 검증 ═══════════════════
  const meta = readMeta(workbook);
  if (!meta) {
    return {
      success: false,
      data: [],
      errors: [],
      warnings: [
        "공식 템플릿이 아닙니다. 템플릿을 다운로드 후 사용해주세요.",
      ],
    };
  }

  // 해시 검증
  const currentHash = regenerateHash(
    currentReferenceData,
    currentDropdownOptions,
    currentItemProcessMappings
  );
  if (meta.version_hash && meta.version_hash !== currentHash) {
    return {
      success: false,
      data: [],
      errors: [],
      warnings: [
        "기준 데이터가 변경되었습니다. 최신 템플릿을 다시 다운로드해주세요.",
      ],
    };
  }

  // ═══════════════════ 2. 시트별 파싱 ═══════════════════
  for (const sheetConfig of config.sheets) {
    const ws = workbook.getWorksheet(sheetConfig.name);
    if (!ws) continue;

    const rows: Record<string, any>[] = [];

    // 헤더 확인 (1행)
    const headerRow = ws.getRow(1);
    const colMap: Record<number, string> = {};
    headerRow.eachCell((cell, colNumber) => {
      const label = cellToString(cell);
      const col = sheetConfig.columns.find((c) => c.label === label);
      if (col) colMap[colNumber] = col.key;
    });

    // 사용자 입력 컬럼만 추출 (autoFill/readOnly/customFormula 제외)
    const userInputKeys = new Set(
      sheetConfig.columns
        .filter((c) => !c.readOnly && !c.autoFill && !c.customFormula)
        .map((c) => c.key)
    );

    // 데이터 행 파싱 (2행부터)
    ws.eachRow((row, rowNumber) => {
      if (rowNumber <= 1) return;

      // 빈 행 체크: 사용자 입력 컬럼만 확인 (수식 셀 무시)
      let hasData = false;
      for (const [colNum, key] of Object.entries(colMap)) {
        if (!userInputKeys.has(key)) continue;
        const val = cellToString(row.getCell(Number(colNum)));
        if (val) { hasData = true; break; }
      }
      if (!hasData) return;

      const rowData: Record<string, any> = {};
      for (const [colNum, key] of Object.entries(colMap)) {
        let value = cellToString(row.getCell(Number(colNum)));

        // 라벨→코드 변환
        if (labelToCodeMap?.[key] && value) {
          const code = labelToCodeMap[key][value];
          if (code) {
            rowData[`${key}_label`] = value;
            value = code;
          }
        }

        rowData[key] = value;
      }

      // ── 행별 검증 ──
      // 필수값 검증
      for (const col of sheetConfig.columns) {
        if (col.readOnly || col.autoFill || col.customFormula) continue;

        const value = rowData[col.key];
        const isEmpty = !value && value !== 0;

        // 조건부 규칙 적용
        // when.column이 autoFill/수식 컬럼이면 값이 미계산일 수 있으므로
        // labelToCodeMap 역매핑 또는 참조데이터에서 직접 조회
        if (config.conditionalRules) {
          const resolveCondValue = (colKey: string): string => {
            // 직접 입력된 값이 있으면 사용
            if (rowData[colKey]) return rowData[colKey];
            // autoFill 컬럼이면: lookup 기준 컬럼의 값으로 참조데이터에서 조회
            const colDef = sheetConfig.columns.find(c => c.key === colKey);
            if (colDef?.autoFill && currentReferenceData.length > 0) {
              const lookupVal = rowData[colDef.autoFill.lookupColumn];
              if (lookupVal) {
                const refRow = currentReferenceData.find(r =>
                  r[config.referenceSheet?.columns?.[0]?.key || "label"] === lookupVal
                );
                if (refRow) return refRow[colDef.autoFill.referenceColumn] || "";
              }
            }
            return "";
          };

          const applicableRules = config.conditionalRules.filter(
            (rule) => resolveCondValue(rule.when.column) === rule.when.equals
          );

          for (const rule of applicableRules) {
            // 필수 컬럼 체크
            if (rule.require.includes(col.key) && isEmpty) {
              const condLabel = sheetConfig.columns.find(c => c.key === rule.when.column)?.label || rule.when.column;
              errors.push({
                sheet: sheetConfig.name,
                row: rowNumber,
                column: col.label,
                message: `${condLabel}이(가) "${rule.when.equals}"일 때 ${col.label}은(는) 필수입니다`,
              });
            }
          }

          // 무시 컬럼이면 검증 스킵
          const isIgnored = applicableRules.some((rule) =>
            rule.ignore.includes(col.key)
          );
          if (isIgnored) continue;
        }

        // 일반 필수값 검증
        if (col.required && isEmpty) {
          errors.push({
            sheet: sheetConfig.name,
            row: rowNumber,
            column: col.label,
            message: `${col.label}은(는) 필수 항목입니다`,
          });
        }
      }

      // 드롭다운 값 유효성 검증
      for (const col of sheetConfig.columns) {
        if (col.type !== "dropdown" || col.readOnly || col.autoFill) continue;

        const value = rowData[col.key];
        if (!value) continue;

        const optionKey = `${sheetConfig.name}:${col.key}`;
        const globalKey = col.key;
        const validValues =
          col.dropdown?.values ||
          currentDropdownOptions[optionKey] ||
          currentDropdownOptions[globalKey] ||
          [];

        if (validValues.length > 0 && !validValues.includes(value)) {
          // 라벨로 입력한 경우도 체크
          const labelValue = rowData[`${col.key}_label`] || value;
          if (!validValues.includes(labelValue)) {
            errors.push({
              sheet: sheetConfig.name,
              row: rowNumber,
              column: col.label,
              message: `유효하지 않은 값: "${value}". 가능한 값: ${validValues.slice(0, 5).join(", ")}${validValues.length > 5 ? " ..." : ""}`,
            });
          }
        }
      }

      // INDIRECT 드롭다운 유효성 검증 (품목별 공정 등)
      if (currentItemProcessMappings && currentItemProcessMappings.length > 0) {
        for (const col of sheetConfig.columns) {
          if (col.dropdown?.source !== "indirect" || col.dropdown.indirectPrefix === "ACC_") continue;
          const value = rowData[col.key];
          if (!value) continue;

          // 품목코드 resolve
          const keyColName = col.dropdown.indirectKeyColumn || "";
          let itemCode = rowData[keyColName] || "";
          // customFormula 컬럼이면 수식 결과가 없을 수 있음 → 품목명으로 역매핑
          if (!itemCode) {
            // 사용자가 입력한 컬럼 중 itemProcessMappings의 itemName과 매칭되는 값 찾기
            for (const [, val] of Object.entries(rowData)) {
              if (val && typeof val === "string") {
                const mapping = currentItemProcessMappings.find(m => m.itemName === val);
                if (mapping) { itemCode = mapping.itemCode; break; }
              }
            }
          }

          if (itemCode) {
            const mapping = currentItemProcessMappings.find(m => m.itemCode === itemCode);
            if (mapping) {
              const valid = mapping.processes.some(p => p.code === value || p.name === value);
              if (!valid) {
                const displayValue = rowData[`${col.key}_label`] || mapping.processes.find(p => p.code === value)?.name || value;
                const validNames = mapping.processes.map(p => p.name);
                errors.push({
                  sheet: sheetConfig.name,
                  row: rowNumber,
                  column: col.label,
                  message: `"${displayValue}"은(는) 해당 품목의 유효한 공정이 아닙니다${validNames.length > 0 ? `. 가능한 공정: ${validNames.join(", ")}` : ""}`,
                });
              }
            }
          }
        }
      }

      rows.push(rowData);
    });

    if (rows.length > 0) {
      data.push({
        sheetName: sheetConfig.name,
        typeKey: sheetConfig.typeKey,
        rows,
      });
    }
  }

  // 데이터 없음 체크
  if (data.length === 0 || data.every((d) => d.rows.length === 0)) {
    warnings.push("업로드할 데이터가 없습니다. 시트에 데이터를 입력해주세요.");
  }

  return {
    success: errors.length === 0 && warnings.length === 0,
    data,
    errors,
    warnings,
  };
}
