import {
  NavigationGraph,
  NavigationEvent,
  NavigationNode,
  NavigationEdge,
  NavigationGraphStats,
  PathResult,
  ToolCallInteraction,
  ExportedGraph,
  UIState,
  BackStackInfo,
  NavigationGraphSummary,
  NavigationGraphSummaryEdge,
  NavigationGraphSummaryNode,
  NavigationGraphSummaryProvider,
  NavigationGraphNodeDetail,
  NavigationGraphNodeResource,
  NavigationGraphNodeResourceProvider
} from "../../src/utils/interfaces/NavigationGraph";

/**
 * Fake implementation of NavigationGraph for testing.
 * Allows full control over navigation graph state and behavior.
 */
export class FakeNavigationGraphManager implements NavigationGraph, NavigationGraphSummaryProvider, NavigationGraphNodeResourceProvider {
  private currentAppId: string | null = null;
  private currentScreen: string | null = null;
  private nodes: Map<string, NavigationNode> = new Map();
  private edges: NavigationEdge[] = [];
  private toolCallHistory: ToolCallInteraction[] = [];
  private nodeSummaryIds: Map<string, number> = new Map();
  private edgeSummaries: NavigationGraphSummaryEdge[] = [];
  private nextNodeId = 1;
  private nextEdgeId = 1;
  private graphUpdateListener?: () => void;

  // Call tracking
  private methodCalls: Map<string, any[][]> = new Map();

  // Configurable responses
  private pathResult: PathResult | null = null;

  // ==================== Configuration Methods ====================

  /**
   * Set the current app ID.
   */
  setCurrentAppId(appId: string | null): void {
    this.currentAppId = appId;
  }

  /**
   * Set the current screen.
   */
  setCurrentScreenValue(screen: string | null): void {
    this.currentScreen = screen;
  }

  /**
   * Add a node to the graph.
   */
  addNode(node: NavigationNode): void {
    this.nodes.set(node.screenName, node);
    if (!this.nodeSummaryIds.has(node.screenName)) {
      this.nodeSummaryIds.set(node.screenName, this.nextNodeId++);
    }
  }

  /**
   * Add an edge to the graph.
   */
  addEdge(edge: NavigationEdge): void {
    this.edges.push(edge);
    this.edgeSummaries.push({
      id: this.nextEdgeId++,
      from: edge.from,
      to: edge.to,
      toolName: edge.interaction?.toolName ?? null
    });
  }

  /**
   * Set the result that findPath will return.
   */
  setPathResult(result: PathResult): void {
    this.pathResult = result;
  }

  /**
   * Add a tool call to the history.
   */
  addToolCallToHistory(interaction: ToolCallInteraction): void {
    this.toolCallHistory.push(interaction);
  }

  // ==================== Call Tracking ====================

  private trackCall(method: string, args: any[]): void {
    if (!this.methodCalls.has(method)) {
      this.methodCalls.set(method, []);
    }
    this.methodCalls.get(method)!.push(args);
  }

  /**
   * Check if a method was called.
   */
  wasMethodCalled(method: string): boolean {
    return (this.methodCalls.get(method)?.length ?? 0) > 0;
  }

  /**
   * Get number of times a method was called.
   */
  getMethodCallCount(method: string): number {
    return this.methodCalls.get(method)?.length ?? 0;
  }

  /**
   * Get the arguments of a specific call to a method.
   */
  getMethodCallArgs(method: string, callIndex: number = 0): any[] | undefined {
    return this.methodCalls.get(method)?.[callIndex];
  }

  /**
   * Clear all call tracking.
   */
  clearCallHistory(): void {
    this.methodCalls.clear();
  }

  /**
   * Reset all state.
   */
  reset(): void {
    this.currentAppId = null;
    this.currentScreen = null;
    this.nodes.clear();
    this.edges = [];
    this.toolCallHistory = [];
    this.nodeSummaryIds.clear();
    this.edgeSummaries = [];
    this.nextNodeId = 1;
    this.nextEdgeId = 1;
    this.graphUpdateListener = undefined;
    this.pathResult = null;
    this.methodCalls.clear();
  }

  // ==================== NavigationGraph Interface ====================

  setCurrentApp(appId: string): void {
    this.trackCall("setCurrentApp", [appId]);
    this.currentAppId = appId;
    this.emitGraphUpdated();
  }

  getCurrentAppId(): string | null {
    this.trackCall("getCurrentAppId", []);
    return this.currentAppId;
  }

  recordNavigationEvent(event: NavigationEvent): void {
    this.trackCall("recordNavigationEvent", [event]);

    const previousScreen = this.currentScreen;

    // Update current screen
    this.currentScreen = event.destination;

    // Add or update node
    if (!this.nodes.has(event.destination)) {
      this.nodes.set(event.destination, {
        screenName: event.destination,
        firstSeenAt: event.timestamp,
        lastSeenAt: event.timestamp,
        visitCount: 1
      });
      this.nodeSummaryIds.set(event.destination, this.nextNodeId++);
    } else {
      const node = this.nodes.get(event.destination)!;
      node.lastSeenAt = event.timestamp;
      node.visitCount++;
    }

    if (previousScreen && previousScreen !== event.destination && this.toolCallHistory.length > 0) {
      const lastInteraction = this.toolCallHistory[this.toolCallHistory.length - 1];
      const edge: NavigationEdge = {
        from: previousScreen,
        to: event.destination,
        timestamp: event.timestamp,
        edgeType: "tool",
        interaction: lastInteraction,
        uiState: lastInteraction.uiState
      };
      this.edges.push(edge);
      this.edgeSummaries.push({
        id: this.nextEdgeId++,
        from: edge.from,
        to: edge.to,
        toolName: edge.interaction?.toolName ?? null
      });
    }

    this.emitGraphUpdated();
  }

  recordBackStack(backStack: BackStackInfo): void {
    this.trackCall("recordBackStack", [backStack]);
  }

  recordToolCall(toolName: string, args: Record<string, any>, uiState?: UIState): void {
    this.trackCall("recordToolCall", [toolName, args, uiState]);
    this.toolCallHistory.push({
      toolName,
      args,
      timestamp: Date.now(),
      uiState
    });
  }

  getCurrentScreen(): string | null {
    this.trackCall("getCurrentScreen", []);
    return this.currentScreen;
  }

  findPath(targetScreen: string): PathResult {
    this.trackCall("findPath", [targetScreen]);

    if (this.pathResult) {
      return this.pathResult;
    }

    // Default behavior: check if we can find a simple path
    if (!this.currentScreen) {
      return {
        found: false,
        path: [],
        startScreen: "",
        targetScreen
      };
    }

    if (this.currentScreen === targetScreen) {
      return {
        found: true,
        path: [],
        startScreen: this.currentScreen,
        targetScreen
      };
    }

    const startScreen = this.currentScreen;
    const queue: Array<{ screen: string; path: NavigationEdge[] }> = [
      { screen: startScreen, path: [] }
    ];
    const visited = new Set<string>([startScreen]);

    while (queue.length > 0) {
      const current = queue.shift()!;
      const outgoing = this.edges.filter(edge => edge.from === current.screen);

      for (const edge of outgoing) {
        if (visited.has(edge.to)) {
          continue;
        }
        const nextPath = [...current.path, edge];
        if (edge.to === targetScreen) {
          return {
            found: true,
            path: nextPath,
            startScreen,
            targetScreen
          };
        }
        visited.add(edge.to);
        queue.push({ screen: edge.to, path: nextPath });
      }
    }

    return {
      found: false,
      path: [],
      startScreen: this.currentScreen,
      targetScreen
    };
  }

  getKnownScreens(): string[] {
    this.trackCall("getKnownScreens", []);
    return Array.from(this.nodes.keys());
  }

  getNode(screenName: string): NavigationNode | undefined {
    this.trackCall("getNode", [screenName]);
    return this.nodes.get(screenName);
  }

  getEdgesFrom(screenName: string): NavigationEdge[] {
    this.trackCall("getEdgesFrom", [screenName]);
    return this.edges.filter(e => e.from === screenName);
  }

  getEdgesTo(screenName: string): NavigationEdge[] {
    this.trackCall("getEdgesTo", [screenName]);
    return this.edges.filter(e => e.to === screenName);
  }

  getStats(): NavigationGraphStats {
    this.trackCall("getStats", []);
    return {
      nodeCount: this.nodes.size,
      edgeCount: this.edges.length,
      currentScreen: this.currentScreen,
      knownEdgeCount: this.edges.filter(e => e.edgeType === "tool").length,
      unknownEdgeCount: this.edges.filter(e => e.edgeType === "unknown").length,
      toolCallHistorySize: this.toolCallHistory.length
    };
  }

  clearCurrentGraph(): void {
    this.trackCall("clearCurrentGraph", []);
    this.nodes.clear();
    this.edges = [];
    this.currentScreen = null;
    this.toolCallHistory = [];
    this.nodeSummaryIds.clear();
    this.edgeSummaries = [];
    this.nextNodeId = 1;
    this.nextEdgeId = 1;
    this.emitGraphUpdated();
  }

  clearAllGraphs(): void {
    this.trackCall("clearAllGraphs", []);
    this.clearCurrentGraph();
    this.currentAppId = null;
    this.emitGraphUpdated();
  }

  exportGraph(): ExportedGraph {
    this.trackCall("exportGraph", []);
    return {
      appId: this.currentAppId,
      nodes: Array.from(this.nodes.values()),
      edges: [...this.edges],
      currentScreen: this.currentScreen
    };
  }

  async exportGraphSummary(): Promise<NavigationGraphSummary> {
    this.trackCall("exportGraphSummary", []);
    const nodes: NavigationGraphSummaryNode[] = Array.from(this.nodes.values()).map(node => ({
      id: this.nodeSummaryIds.get(node.screenName) ?? this.nextNodeId++,
      screenName: node.screenName,
      visitCount: node.visitCount
    }));

    return {
      appId: this.currentAppId,
      nodes,
      edges: [...this.edgeSummaries],
      currentScreen: this.currentScreen
    };
  }

  setGraphUpdateListener(listener: (() => void) | null): void {
    this.graphUpdateListener = listener ?? undefined;
  }

  private emitGraphUpdated(): void {
    if (this.graphUpdateListener) {
      this.graphUpdateListener();
    }
  }

  private buildNodeResource(node: NavigationNode, nodeId: number): NavigationGraphNodeResource {
    const detail: NavigationGraphNodeDetail = {
      id: nodeId,
      ...node
    };

    return {
      appId: this.currentAppId,
      node: detail,
      isCurrentScreen: this.currentScreen === node.screenName,
      edgesFrom: this.edges.filter(edge => edge.from === node.screenName),
      edgesTo: this.edges.filter(edge => edge.to === node.screenName),
    };
  }

  async getNodeResourceById(nodeId: number): Promise<NavigationGraphNodeResource | null> {
    this.trackCall("getNodeResourceById", [nodeId]);

    if (!this.currentAppId) {
      return null;
    }

    const screenName = Array.from(this.nodeSummaryIds.entries()).find(
      ([, id]) => id === nodeId
    )?.[0];

    if (!screenName) {
      return null;
    }

    const node = this.nodes.get(screenName);
    if (!node) {
      return null;
    }

    return this.buildNodeResource(node, nodeId);
  }

  async getNodeResourceByScreen(screenName: string): Promise<NavigationGraphNodeResource | null> {
    this.trackCall("getNodeResourceByScreen", [screenName]);

    if (!this.currentAppId) {
      return null;
    }

    const node = this.nodes.get(screenName);
    if (!node) {
      return null;
    }

    const nodeId = this.nodeSummaryIds.get(screenName) ?? this.nextNodeId++;
    this.nodeSummaryIds.set(screenName, nodeId);

    return this.buildNodeResource(node, nodeId);
  }
}
