/**
 * 노드 플로우 검증 유틸리티
 *
 * 감지 가능한 문제:
 * 1. 병렬 실행 시 동일 테이블/컬럼 충돌
 * 2. WHERE 조건 누락 (전체 테이블 삭제/업데이트)
 * 3. 순환 참조 (무한 루프)
 * 4. 데이터 소스 타입 불일치
 */

export type ValidationSeverity = "error" | "warning" | "info";

export interface FlowValidation {
  nodeId: string;
  severity: ValidationSeverity;
  type: string;
  message: string;
  affectedNodes?: string[];
}

import type { FlowNode as TypedFlowNode, FlowEdge as TypedFlowEdge } from "@/types/node-editor";

export type FlowNode = TypedFlowNode;
export type FlowEdge = TypedFlowEdge;

/**
 * 플로우 전체 검증
 */
export function validateFlow(nodes: FlowNode[], edges: FlowEdge[]): FlowValidation[] {
  const validations: FlowValidation[] = [];

  // 0. 연결되지 않은 노드 검증 (최우선)
  validations.push(...detectDisconnectedNodes(nodes, edges));

  // 1. 병렬 실행 충돌 검증
  validations.push(...detectParallelConflicts(nodes, edges));

  // 2. WHERE 조건 누락 검증
  validations.push(...detectMissingWhereConditions(nodes));

  // 3. 순환 참조 검증
  validations.push(...detectCircularReferences(nodes, edges));

  // 4. 데이터 소스 타입 불일치 검증
  validations.push(...detectDataSourceMismatch(nodes, edges));

  return validations;
}

/**
 * 연결되지 않은 노드(고아 노드) 감지
 */
function detectDisconnectedNodes(nodes: FlowNode[], edges: FlowEdge[]): FlowValidation[] {
  const validations: FlowValidation[] = [];

  // 노드가 없으면 검증 스킵
  if (nodes.length === 0) {
    return validations;
  }

  // 연결된 노드 ID 수집
  const connectedNodeIds = new Set<string>();
  for (const edge of edges) {
    connectedNodeIds.add(edge.source);
    connectedNodeIds.add(edge.target);
  }

  // Comment 노드는 고아 노드여도 괜찮음 (메모 용도)
  const disconnectedNodes = nodes.filter((node) => !connectedNodeIds.has(node.id) && node.type !== "comment");

  // 고아 노드가 있으면 경고
  for (const node of disconnectedNodes) {
    validations.push({
      nodeId: node.id,
      severity: "warning",
      type: "disconnected-node",
      message: `"${node.data.displayName || node.type}" 노드가 다른 노드와 연결되어 있지 않습니다. 이 노드는 실행되지 않습니다.`,
    });
  }

  return validations;
}

/**
 * 특정 노드에서 도달 가능한 모든 노드 찾기 (DFS)
 */
function getReachableNodes(startNodeId: string, allNodes: FlowNode[], edges: FlowEdge[]): FlowNode[] {
  const reachable = new Set<string>();
  const visited = new Set<string>();

  function dfs(nodeId: string) {
    if (visited.has(nodeId)) return;
    visited.add(nodeId);
    reachable.add(nodeId);

    const outgoingEdges = edges.filter((e) => e.source === nodeId);
    for (const edge of outgoingEdges) {
      dfs(edge.target);
    }
  }

  dfs(startNodeId);

  return allNodes.filter((node) => reachable.has(node.id));
}

/**
 * 병렬 실행 시 동일 테이블/컬럼 충돌 감지
 */
function detectParallelConflicts(nodes: FlowNode[], edges: FlowEdge[]): FlowValidation[] {
  const validations: FlowValidation[] = [];

  // 🆕 연결된 노드만 필터링 (고아 노드 제외)
  const connectedNodeIds = new Set<string>();
  for (const edge of edges) {
    connectedNodeIds.add(edge.source);
    connectedNodeIds.add(edge.target);
  }

  // 🆕 소스 노드 찾기
  const sourceNodes = nodes.filter(
    (node) =>
      (node.type === "tableSource" || node.type === "externalDBSource" || node.type === "restAPISource") &&
      connectedNodeIds.has(node.id),
  );

  // 각 소스 노드에서 시작하는 플로우별로 검증
  for (const sourceNode of sourceNodes) {
    // 이 소스에서 도달 가능한 모든 노드 찾기
    const reachableNodes = getReachableNodes(sourceNode.id, nodes, edges);

    // 레벨별로 그룹화
    const levels = groupNodesByLevel(
      reachableNodes,
      edges.filter(
        (e) => reachableNodes.some((n) => n.id === e.source) && reachableNodes.some((n) => n.id === e.target),
      ),
    );

    // 각 레벨에서 충돌 검사
    for (const [levelNum, levelNodes] of levels.entries()) {
      const updateNodes = levelNodes.filter((node) => node.type === "updateAction" || node.type === "deleteAction");

      if (updateNodes.length < 2) continue;

      // 🆕 조건 노드로 분기된 노드들인지 확인
      // 같은 레벨의 노드들이 조건 노드를 통해 분기되었다면 병렬이 아님
      const parentNodes = updateNodes.map((node) => {
        const incomingEdge = edges.find((e) => e.target === node.id);
        return incomingEdge ? nodes.find((n) => n.id === incomingEdge.source) : null;
      });

      // 모든 부모 노드가 같은 조건 노드라면 병렬이 아닌 조건 분기
      const uniqueParents = new Set(parentNodes.map((p) => p?.id).filter(Boolean));
      const isConditionalBranch = uniqueParents.size === 1 && parentNodes[0]?.type === "condition";

      if (isConditionalBranch) {
        // 조건 분기는 순차 실행이므로 병렬 충돌 검사 스킵
        continue;
      }

      // 같은 테이블을 수정하는 노드들 찾기
      const tableMap = new Map<string, FlowNode[]>();

      for (const node of updateNodes) {
        const tableName = node.data.targetTable || node.data.externalTargetTable;
        if (tableName) {
          if (!tableMap.has(tableName)) {
            tableMap.set(tableName, []);
          }
          tableMap.get(tableName)!.push(node);
        }
      }

      // 충돌 검사
      for (const [tableName, conflictNodes] of tableMap.entries()) {
        if (conflictNodes.length > 1) {
          // 같은 컬럼을 수정하는지 확인
          const fieldMap = new Map<string, FlowNode[]>();

          for (const node of conflictNodes) {
            const fields = node.data.fieldMappings?.map((m: any) => m.targetField) || [];
            for (const field of fields) {
              if (!fieldMap.has(field)) {
                fieldMap.set(field, []);
              }
              fieldMap.get(field)!.push(node);
            }
          }

          for (const [field, fieldNodes] of fieldMap.entries()) {
            if (fieldNodes.length > 1) {
              validations.push({
                nodeId: fieldNodes[0].id,
                severity: "warning",
                type: "parallel-conflict",
                message: `병렬 실행 중 '${tableName}.${field}' 컬럼에 대한 충돌 가능성이 있습니다. 실행 순서가 보장되지 않아 예상치 못한 결과가 발생할 수 있습니다.`,
                affectedNodes: fieldNodes.map((n) => n.id),
              });
            }
          }

          // 같은 테이블에 대한 일반 경고
          if (conflictNodes.length > 1 && fieldMap.size === 0) {
            validations.push({
              nodeId: conflictNodes[0].id,
              severity: "info",
              type: "parallel-table-access",
              message: `병렬 실행 중 '${tableName}' 테이블을 동시에 수정합니다.`,
              affectedNodes: conflictNodes.map((n) => n.id),
            });
          }
        }
      }
    }
  }

  return validations;
}

/**
 * WHERE 조건 누락 감지
 */
function detectMissingWhereConditions(nodes: FlowNode[]): FlowValidation[] {
  const validations: FlowValidation[] = [];

  for (const node of nodes) {
    if (node.type === "updateAction" || node.type === "deleteAction") {
      const whereConditions = node.data.whereConditions;

      if (!whereConditions || whereConditions.length === 0) {
        validations.push({
          nodeId: node.id,
          severity: "error",
          type: "missing-where",
          message: `WHERE 조건 없이 전체 테이블을 ${node.type === "deleteAction" ? "삭제" : "수정"}합니다. 매우 위험합니다!`,
        });
      }
    }
  }

  return validations;
}

/**
 * 순환 참조 감지 (무한 루프)
 */
function detectCircularReferences(nodes: FlowNode[], edges: FlowEdge[]): FlowValidation[] {
  const validations: FlowValidation[] = [];

  // 인접 리스트 생성
  const adjacencyList = new Map<string, string[]>();
  for (const node of nodes) {
    adjacencyList.set(node.id, []);
  }
  for (const edge of edges) {
    adjacencyList.get(edge.source)?.push(edge.target);
  }

  // DFS로 순환 참조 찾기
  const visited = new Set<string>();
  const recursionStack = new Set<string>();
  const cycles: string[][] = [];

  function dfs(nodeId: string, path: string[]): void {
    visited.add(nodeId);
    recursionStack.add(nodeId);
    path.push(nodeId);

    const neighbors = adjacencyList.get(nodeId) || [];
    for (const neighbor of neighbors) {
      if (!visited.has(neighbor)) {
        dfs(neighbor, [...path]);
      } else if (recursionStack.has(neighbor)) {
        // 순환 참조 발견
        const cycleStart = path.indexOf(neighbor);
        const cycle = path.slice(cycleStart);
        cycles.push([...cycle, neighbor]);
      }
    }

    recursionStack.delete(nodeId);
  }

  for (const node of nodes) {
    if (!visited.has(node.id)) {
      dfs(node.id, []);
    }
  }

  // 순환 참조 경고 생성
  for (const cycle of cycles) {
    const nodeNames = cycle
      .map((id) => {
        const node = nodes.find((n) => n.id === id);
        return node?.data.displayName || node?.type || id;
      })
      .join(" → ");

    validations.push({
      nodeId: cycle[0],
      severity: "error",
      type: "circular-reference",
      message: `순환 참조가 감지되었습니다: ${nodeNames}`,
      affectedNodes: cycle,
    });
  }

  return validations;
}

/**
 * 데이터 소스 타입 불일치 감지
 */
function detectDataSourceMismatch(nodes: FlowNode[], edges: FlowEdge[]): FlowValidation[] {
  const validations: FlowValidation[] = [];

  // 각 노드의 데이터 소스 타입 추적
  const nodeDataSourceTypes = new Map<string, string>();

  // Source 노드들의 타입 수집
  for (const node of nodes) {
    if (node.type === "tableSource" || node.type === "externalDBSource") {
      const dataSourceType = node.data.dataSourceType || "context-data";
      nodeDataSourceTypes.set(node.id, dataSourceType);
    }
  }

  // 각 엣지를 따라 데이터 소스 타입 전파
  for (const edge of edges) {
    const sourceType = nodeDataSourceTypes.get(edge.source);
    if (sourceType) {
      nodeDataSourceTypes.set(edge.target, sourceType);
    }
  }

  // Action 노드들 검사
  for (const node of nodes) {
    if (node.type === "updateAction" || node.type === "deleteAction" || node.type === "insertAction") {
      const dataSourceType = nodeDataSourceTypes.get(node.id);

      // table-all 모드인데 WHERE에 특정 레코드 조건이 있는 경우
      if (dataSourceType === "table-all") {
        const whereConditions = node.data.whereConditions || [];
        const hasPrimaryKeyCondition = whereConditions.some((cond: any) => cond.field === "id");

        if (hasPrimaryKeyCondition) {
          validations.push({
            nodeId: node.id,
            severity: "warning",
            type: "data-source-mismatch",
            message: `데이터 소스가 'table-all'이지만 WHERE 조건에 Primary Key가 포함되어 있습니다. 의도한 동작인지 확인하세요.`,
          });
        }
      }
    }
  }

  return validations;
}

/**
 * 레벨별로 노드 그룹화 (위상 정렬)
 */
function groupNodesByLevel(nodes: FlowNode[], edges: FlowEdge[]): Map<number, FlowNode[]> {
  const levels = new Map<number, FlowNode[]>();
  const nodeLevel = new Map<string, number>();
  const inDegree = new Map<string, number>();
  const adjacencyList = new Map<string, string[]>();

  // 초기화
  for (const node of nodes) {
    inDegree.set(node.id, 0);
    adjacencyList.set(node.id, []);
  }

  // 인접 리스트 및 진입 차수 계산
  for (const edge of edges) {
    adjacencyList.get(edge.source)?.push(edge.target);
    inDegree.set(edge.target, (inDegree.get(edge.target) || 0) + 1);
  }

  // BFS로 레벨 계산
  const queue: string[] = [];
  for (const [nodeId, degree] of inDegree.entries()) {
    if (degree === 0) {
      queue.push(nodeId);
      nodeLevel.set(nodeId, 0);
    }
  }

  while (queue.length > 0) {
    const currentId = queue.shift()!;
    const currentLevel = nodeLevel.get(currentId)!;

    const neighbors = adjacencyList.get(currentId) || [];
    for (const neighbor of neighbors) {
      const newDegree = (inDegree.get(neighbor) || 0) - 1;
      inDegree.set(neighbor, newDegree);

      if (newDegree === 0) {
        queue.push(neighbor);
        nodeLevel.set(neighbor, currentLevel + 1);
      }
    }
  }

  // 레벨별로 노드 그룹화
  for (const node of nodes) {
    const level = nodeLevel.get(node.id) || 0;
    if (!levels.has(level)) {
      levels.set(level, []);
    }
    levels.get(level)!.push(node);
  }

  return levels;
}

/**
 * 검증 결과 요약
 */
export function summarizeValidations(validations: FlowValidation[]): {
  errorCount: number;
  warningCount: number;
  infoCount: number;
  hasBlockingIssues: boolean;
} {
  const errorCount = validations.filter((v) => v.severity === "error").length;
  const warningCount = validations.filter((v) => v.severity === "warning").length;
  const infoCount = validations.filter((v) => v.severity === "info").length;

  return {
    errorCount,
    warningCount,
    infoCount,
    hasBlockingIssues: errorCount > 0,
  };
}

/**
 * 특정 노드의 검증 결과 가져오기
 */
export function getNodeValidations(nodeId: string, validations: FlowValidation[]): FlowValidation[] {
  return validations.filter((v) => v.nodeId === nodeId || v.affectedNodes?.includes(nodeId));
}
