import { useCallback, useRef, useState } from 'react';

export class ScenarioExecutor {
  constructor(nodes, edges, apiClient, onLogAppend, onNodeStatusChange) {
    this.nodes = nodes;
    this.edges = edges;
    this.apiClient = apiClient;
    this.onLogAppend = onLogAppend;
    this.onNodeStatusChange = onNodeStatusChange;
    this.reset();
  }

  reset() {
    this.currentNodeId = this.findStartNode()?.id;
    this.visitedNodes = new Set();
    this.isRunning = false;
    this.isPaused = false;
    this.isDone = false;
    this.nodes.forEach(node => {
      this.onNodeStatusChange?.(node.id, 'default');
    });
  }

  pause() {
    this.isPaused = true;
    this.isRunning = false;
  }

  resume() {
    this.isPaused = false;
    this.isRunning = true;
  }

  findStartNode() {
    return this.nodes.find(node => node.type === 'startNode');
  }

  findNextNodes(nodeId) {
    return this.edges
    .filter(edge => edge.source === nodeId)
    .map(edge => ({
      nodeId: edge.target,
      condition: edge.sourceHandle
    }));
  }

  async executeCurrentNode() {
    if (!this.currentNodeId || this.isPaused) return;
    console.log(this.currentNodeId);
    const currentNode = this.nodes.find(n => n.id === this.currentNodeId);
    console.log({currentNode});
    if (!currentNode) return;

    this.isRunning = true;
    this.visitedNodes.add(this.currentNodeId);
    this.onNodeStatusChange?.(this.currentNodeId, 'running');

    try {
      let result;
      switch (currentNode.type) {
        case 'startNode':
          this.appendLog('Starting API scenario\n');
          break;

        case 'apiNode':
          result = await this.executeApiNode(currentNode);
          break;

        case 'delayNode':
          await this.executeDelayNode(currentNode);
          break;

        case 'conditionNode':
          result = await this.executeConditionNode(currentNode);
          break;
      }

      this.onNodeStatusChange?.(this.currentNodeId, 'success');
      return result;
    } catch (error) {
      console.error(error);
      this.onNodeStatusChange?.(this.currentNodeId, 'error');
      this.appendLog(`Error executing node ${currentNode.id}: ${error.message}`);
      throw error;
    }
  }

  async executeApiNode(node) {
    const { data } = node;
    this.appendLog(`Executing API [${data.name}]`);

    try {
      const response = await this.apiClient.sendAPIRequest(data, this.appendLog.bind(this));
      console.log({response});
      this.logApiResponse(response, data.type);
      return response;
    } catch (error) {
      throw new Error(`API Error: ${error.message}`);
    }
  }

  async executeDelayNode(node) {
    const duration = node.data.duration || 1000;
    this.appendLog(`Delay: ${duration}ms`);
    await new Promise(resolve => setTimeout(resolve, duration));
  }

  async executeConditionNode(node) {
    const { condition, script } = node.data;
    this.appendLog(`Evaluating condition: ${condition}`);

    try {
      const result = await this.evaluateCondition(script || condition);
      this.appendLog(`Condition result: ${result}`);
      return result;
    } catch (error) {
      throw new Error(`Condition evaluation failed: ${error.message}`);
    }
  }

  async evaluateCondition(script) {
    return new Promise((resolve, reject) => {
      const scriptExecutor = document.createElement('iframe');
      scriptExecutor.style.display = 'none';
      document.body.appendChild(scriptExecutor);

      window.conditionComplete = (result, error) => {
        document.body.removeChild(scriptExecutor);
        if (error) reject(error);
        else resolve(!!result);
      };

      const scriptContent = `
        <script>
          try {
            const result = (function() { ${script} })();
            window.parent.conditionComplete(result);
          } catch (error) {
            window.parent.conditionComplete(null, error);
          }
        </script>
      `;

      scriptExecutor.srcdoc = scriptContent;
    });
  }

  appendLog(text) {
    this.onLogAppend(text + '\n\n');
  }

  logApiResponse(response, type) {
    const responseLog = type === 'HTTP'
        ? `Response:\nStatus: ${response.status}\nHeaders:\n${this.formatHeaders(response.headers)}\nBody:\n${response.body}`
        : `Response:\n${response.body}`;

    this.appendLog(responseLog);
  }

  formatHeaders(headers) {
    return Object.entries(headers)
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([key, value]) => `${key}: ${value}`)
    .join('\n');
  }

  async executeAll() {
    while (this.currentNodeId && !this.isDone && !this.isPaused) {
      try {
        await this.executeCurrentNode();
        await this.moveToNextNode();
      } catch (error) {
        this.appendLog(`Execution stopped: ${error.message}`);
        break;
      }
    }

    if (this.isDone) {
      this.appendLog('Scenario execution completed');
    } else if (this.isPaused) {
      this.appendLog('Scenario execution paused');
    }
  }

  async moveToNextNode() {
    const nextNodes = this.findNextNodes(this.currentNodeId);

    if (nextNodes.length === 0) {
      this.isDone = true;
      this.currentNodeId = null;
      return;
    }

    const currentNode = this.nodes.find(n => n.id === this.currentNodeId);

    if (currentNode.type === 'conditionNode') {
      const result = await this.evaluateCondition(currentNode.data.script || currentNode.data.condition);
      const nextNode = nextNodes.find(n => n.condition === (result ? 'true' : 'false'));
      this.currentNodeId = nextNode?.nodeId;
    } else {
      const nextNode = nextNodes.find(n => !this.visitedNodes.has(n.nodeId));
      this.currentNodeId = nextNode?.nodeId;
    }

    if (!this.currentNodeId) {
      this.isDone = true;
    }
  }
}

export const useScenarioExecutor = (nodes, edges, apiClient) => {
  const [logs, setLogs] = useState('');
  const [nodeStatuses, setNodeStatuses] = useState({});
  const executorRef = useRef(null);

  const appendLog = useCallback((text) => {
    setLogs(prev => prev + text);
  }, []);

  const updateNodeStatus = useCallback((nodeId, status) => {
    setNodeStatuses(prev => ({ ...prev, [nodeId]: status }));
  }, []);

  const initExecutor = useCallback(() => {
    executorRef.current = new ScenarioExecutor(
        nodes,
        edges,
        apiClient,
        appendLog,
        updateNodeStatus
    );
  }, [nodes, edges, apiClient]);

  const executeStep = useCallback(async () => {
    if (!executorRef.current) initExecutor();
    if (!executorRef.current.isDone) {
      await executorRef.current.executeCurrentNode();
      await executorRef.current.moveToNextNode();
    }
  }, [initExecutor]);

  const executeScenario = useCallback(async () => {
    if (!executorRef.current) initExecutor();
    await executorRef.current.executeAll();
  }, [initExecutor]);

  const pauseExecution = useCallback(() => {
    if (executorRef.current) {
      executorRef.current.pause();
    }
  }, []);

  const resumeExecution = useCallback(async () => {
    if (executorRef.current) {
      executorRef.current.resume();
      await executorRef.current.executeAll();
    }
  }, []);

  return {
    logs,
    nodeStatuses,
    clearLogs: () => setLogs(''),
    executeScenario,
    executeStep,
    pauseExecution,
    resumeExecution,
    resetExecutor: initExecutor,
    isRunning: executorRef.current?.isRunning ?? false,
    isPaused: executorRef.current?.isPaused ?? false,
    isDone: executorRef.current?.isDone ?? false
  };
};
