/**
 * 데이터 매핑 유틸리티
 * 화면 간 데이터 전달 시 매핑 규칙 적용
 */

import type {
  MappingRule,
  Condition,
  TransformFunction,
} from "@/types/screen-embedding";
import type { EntityJoinColumnMeta } from "@/types/data-transfer";
import { logger } from "./logger";

/**
 * 매핑 규칙 적용
 * @param data 배열 또는 단일 객체
 * @param rules 매핑 규칙 배열
 * @param entityJoinColumns 엔티티 조인 메타데이터 (선택적) - sourceField 값이 비었을 때 조인 alias에서 해결
 * @returns 매핑된 배열
 */
export function applyMappingRules(
  data: any[] | any,
  rules: MappingRule[],
  entityJoinColumns?: EntityJoinColumnMeta[],
): any[] {
  // 빈 데이터 처리
  if (!data) {
    return [];
  }
  
  // 배열이 아닌 경우 배열로 변환
  const dataArray = Array.isArray(data) ? data : [data];
  
  if (dataArray.length === 0) {
    return [];
  }
  
  // 규칙이 없으면 원본 데이터 반환
  if (!rules || rules.length === 0) {
    return dataArray;
  }

  // 변환 함수가 있는 규칙 확인
  const hasTransform = rules.some((rule) => rule.transform && rule.transform !== "none");

  if (hasTransform) {
    // 변환 함수가 있으면 단일 값 또는 집계 결과 반환
    return [applyTransformRules(dataArray, rules)];
  }

  // 엔티티 조인 alias 역방향 맵 구성: { referenceColumn → joinAlias }
  // ex) joinAlias "part_code_item_name" → sourceColumn "part_code", referenceColumn "item_name"
  const joinAliasMap = buildJoinAliasMap(entityJoinColumns);

  // 일반 매핑 (각 행에 대해 매핑)
  // 원본 데이터를 복사한 후 매핑 규칙 적용 (매핑되지 않은 필드도 유지)
  return dataArray.map((row) => {
    const mappedRow: any = { ...row };

    for (const rule of rules) {
      if (!rule.sourceField || !rule.targetField) {
        continue;
      }
      
      let sourceValue = getNestedValue(row, rule.sourceField);

      // sourceField 값이 비어있으면 엔티티 조인 alias에서 해결 시도
      if (isEmptyValue(sourceValue) && joinAliasMap.size > 0) {
        sourceValue = resolveFromEntityJoin(row, rule.targetField, joinAliasMap);
        if (sourceValue !== undefined) {
          logger.info(
            `[dataMapping] 엔티티 조인 해결: ${rule.sourceField}(비어있음) → targetField "${rule.targetField}" → alias에서 값 획득`,
          );
        }
      }

      const targetValue = sourceValue ?? rule.defaultValue;

      if (rule.sourceField !== rule.targetField) {
        delete mappedRow[rule.sourceField];
      }
      
      setNestedValue(mappedRow, rule.targetField, targetValue);
    }

    return mappedRow;
  });
}

/**
 * 엔티티 조인 alias에서 역방향 참조 맵 구성
 * joinAlias 네이밍 규칙: {sourceColumn}_{referenceColumn}
 * 예: "part_code_item_name" → sourceColumn="part_code", referenceColumn="item_name"
 *
 * 반환 Map: referenceColumn → joinAlias
 * 예: "item_name" → "part_code_item_name"
 */
function buildJoinAliasMap(
  entityJoinColumns?: EntityJoinColumnMeta[],
): Map<string, string> {
  const map = new Map<string, string>();
  if (!entityJoinColumns || entityJoinColumns.length === 0) return map;

  for (const meta of entityJoinColumns) {
    const prefix = `${meta.sourceColumn}_`;
    if (meta.joinAlias.startsWith(prefix)) {
      const referenceColumn = meta.joinAlias.slice(prefix.length);
      map.set(referenceColumn, meta.joinAlias);
    }
  }
  return map;
}

/**
 * 엔티티 조인 alias에서 targetField에 해당하는 값 해결
 * targetField 이름으로 조인 alias를 찾아 row에서 값을 가져옴
 */
function resolveFromEntityJoin(
  row: any,
  targetField: string,
  joinAliasMap: Map<string, string>,
): any {
  const joinAlias = joinAliasMap.get(targetField);
  if (!joinAlias) return undefined;

  const value = row[joinAlias];
  return isEmptyValue(value) ? undefined : value;
}

function isEmptyValue(value: any): boolean {
  return value === null || value === undefined || value === "";
}

/**
 * 변환 함수 적용
 */
function applyTransformRules(data: any[], rules: MappingRule[]): any {
  const result: any = {};

  for (const rule of rules) {
    const values = data.map((row) => getNestedValue(row, rule.sourceField));
    const transformedValue = applyTransform(values, rule.transform || "none");

    setNestedValue(result, rule.targetField, transformedValue);
  }

  return result;
}

/**
 * 변환 함수 실행
 */
function applyTransform(values: any[], transform: TransformFunction): any {
  switch (transform) {
    case "none":
      return values;

    case "sum":
      return values.reduce((sum, val) => sum + (Number(val) || 0), 0);

    case "average":
      const sum = values.reduce((s, val) => s + (Number(val) || 0), 0);
      return values.length > 0 ? sum / values.length : 0;

    case "count":
      return values.length;

    case "min":
      return Math.min(...values.map((v) => Number(v) || 0));

    case "max":
      return Math.max(...values.map((v) => Number(v) || 0));

    case "first":
      return values[0];

    case "last":
      return values[values.length - 1];

    case "concat":
      return values.filter((v) => v != null).join("");

    case "join":
      return values.filter((v) => v != null).join(", ");

    case "custom":
      // TODO: 커스텀 함수 실행
      logger.warn("커스텀 변환 함수는 아직 구현되지 않았습니다.");
      return values;

    default:
      return values;
  }
}

/**
 * 조건에 따른 데이터 필터링
 */
export function filterDataByCondition(data: any[], condition: Condition): any[] {
  return data.filter((row) => {
    const value = getNestedValue(row, condition.field);
    return evaluateCondition(value, condition.operator, condition.value);
  });
}

/**
 * 조건 평가
 */
function evaluateCondition(value: any, operator: string, targetValue: any): boolean {
  switch (operator) {
    case "equals":
      return value === targetValue;

    case "notEquals":
      return value !== targetValue;

    case "contains":
      return String(value).includes(String(targetValue));

    case "notContains":
      return !String(value).includes(String(targetValue));

    case "greaterThan":
      return Number(value) > Number(targetValue);

    case "lessThan":
      return Number(value) < Number(targetValue);

    case "greaterThanOrEqual":
      return Number(value) >= Number(targetValue);

    case "lessThanOrEqual":
      return Number(value) <= Number(targetValue);

    case "in":
      return Array.isArray(targetValue) && targetValue.includes(value);

    case "notIn":
      return Array.isArray(targetValue) && !targetValue.includes(value);

    default:
      logger.warn(`알 수 없는 조건 연산자: ${operator}`);
      return true;
  }
}

/**
 * 중첩된 객체에서 값 가져오기
 * 예: "user.address.city" -> obj.user.address.city
 */
function getNestedValue(obj: any, path: string): any {
  if (!obj || !path) {
    return undefined;
  }

  const keys = path.split(".");
  let value = obj;

  for (const key of keys) {
    if (value == null) {
      return undefined;
    }
    value = value[key];
  }

  return value;
}

/**
 * 중첩된 객체에 값 설정
 * 예: "user.address.city", "Seoul" -> obj.user.address.city = "Seoul"
 */
function setNestedValue(obj: any, path: string, value: any): void {
  if (!obj || !path) {
    return;
  }

  const keys = path.split(".");
  const lastKey = keys.pop()!;
  let current = obj;

  for (const key of keys) {
    if (!(key in current)) {
      current[key] = {};
    }
    current = current[key];
  }

  current[lastKey] = value;
}

/**
 * 매핑 결과 검증
 */
export function validateMappingResult(
  data: any[],
  rules: MappingRule[]
): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  // 필수 필드 검증
  const requiredRules = rules.filter((rule) => rule.required);

  for (const rule of requiredRules) {
    const hasValue = data.some((row) => {
      const value = getNestedValue(row, rule.targetField);
      return value != null && value !== "";
    });

    if (!hasValue) {
      errors.push(`필수 필드 누락: ${rule.targetField}`);
    }
  }

  return {
    valid: errors.length === 0,
    errors,
  };
}

/**
 * 매핑 규칙 미리보기
 * 실제 데이터 전달 전에 결과를 미리 확인
 */
export function previewMapping(
  sampleData: any[],
  rules: MappingRule[]
): { success: boolean; preview: any[]; errors?: string[] } {
  try {
    const preview = applyMappingRules(sampleData.slice(0, 5), rules);
    const validation = validateMappingResult(preview, rules);

    return {
      success: validation.valid,
      preview,
      errors: validation.errors,
    };
  } catch (error: any) {
    return {
      success: false,
      preview: [],
      errors: [error.message],
    };
  }
}

