package com.eactive.testmaster.loadtest.service;

import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.dto.ApiRequestKeyValueDTO;
import com.eactive.testmaster.client.dto.ApiResponse;
import com.eactive.testmaster.client.dto.internal.ScenarioNode;
import com.eactive.testmaster.client.service.NettyApiClient;
import com.eactive.testmaster.loadtest.dto.LoadTestRequestAndResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.lang3.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class VirtualUser implements Runnable {

    //logger
    private static final Logger logger = LoggerFactory.getLogger(VirtualUser.class);
    private final Object pauseLock = new Object();
    private boolean paused = false;
    private volatile boolean running = true;

    private int maxCount;

    private Thread runningThread;

    private final LoadTestService loadTestService;
    private final NettyApiClient nettyApiClient;

    private static Map<String, String> globals = new ConcurrentHashMap<>();
    private Map<String, String> variables = new ConcurrentHashMap<>();

    private String testId;

    private ScenarioNode scenario;

    private Set<ScenarioNode> visitedNodes = new HashSet<>();

    private CountDownLatch latch;

    public VirtualUser(String testId, int maxCount, ScenarioNode scenarioDTO, LoadTestService loadTestService, NettyApiClient nettyApiClient, CountDownLatch latch) {
        this.testId = testId;
        this.scenario = scenarioDTO;
        this.loadTestService = loadTestService;
        this.nettyApiClient = nettyApiClient;
        this.maxCount = maxCount;
        this.latch = latch;

    }

    public void pause() {
        synchronized (pauseLock) {
            paused = true;
        }
    }

    public void resume() {
        synchronized (pauseLock) {
            paused = false;
            pauseLock.notifyAll();
        }
    }

    public void stop() {
        running = false;
        latch.countDown();
        resume(); // To ensure the user exits if it's paused
        if (runningThread != null) {
            logger.debug("Virtual User Interrupting thread");
            runningThread.interrupt();
        }
    }

    @Override
    public void run() {
        try {

            logger.debug("Virtual user started");
            runningThread = Thread.currentThread();
            int count = 0;
            while (maxCount == 0 || running && count < maxCount) {
                synchronized (pauseLock) {
                    if (paused) {
                        try {
                            pauseLock.wait();
                        } catch (InterruptedException ex) {
                            Thread.currentThread().interrupt(); // Preserve interrupt status
                            break;
                        }
                    }
                }
                if (!running) {
                    break;
                }
                //clear visited nodes
                visitedNodes.clear();
                performAction();
                logger.debug("Count: " + (count + 1) + " of " + maxCount);
                count++;
            }
        } finally {
            latch.countDown();
        }
    }

    private void performAction() {
        ScenarioNode currentNode = scenario;

        while (running) {
            if (Thread.interrupted()) {
                break;
            }
            if (currentNode != null) {
                if (visitedNodes.contains(currentNode)) {
                    logger.debug("Loop detected, stopping");
                    break;
                }
                visitedNodes.add(currentNode);

                if (currentNode.getClassName().equalsIgnoreCase("api")) {
                    processApi(currentNode);
                }

                if (currentNode.getConnections() != null && !currentNode.getConnections().isEmpty()) {
                    currentNode = currentNode.getConnections().get(0); //현재는 1개의 connection만 지원
                } else {
                    break;
                }
            }
        }
        logger.debug("Action performed or interrupted");
    }

    private void processApi(ScenarioNode currentNode) {
        try {
            if (currentNode.getApiRequest() != null) {
                ApiRequestDTO cloned = SerializationUtils.clone(currentNode.getApiRequest());

                executePreRequestScript(cloned);
                ApiRequestDTO replaced = replacePlaceHoldersWithVariables(cloned);

                long startTime = System.currentTimeMillis();
                ApiResponse response = nettyApiClient.handleRequest(replaced).get();
                long endTime = System.currentTimeMillis();
                long responseTime = endTime - startTime;

                executePostRequestScript(replaced, response);
                LoadTestRequestAndResponse requestAndResponse = new LoadTestRequestAndResponse(replaced, response, responseTime);
                loadTestService.sendLoadTestUpdate(testId, requestAndResponse);
            }
        } catch (InterruptedException | ExecutionException e) {
            loadTestService.sendLoadTestError(testId, "An error occurred: " + e.getMessage());
        }
    }

    private void executePreRequestScript(ApiRequestDTO apiRequest) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");
        engine.put("console", new Console());
        engine.put("globals", globals);
        engine.put("variables", variables);
        engine.put("request", apiRequest);
        try {
            engine.eval(apiRequest.getPreRequestScript());
        } catch (ScriptException e) {
            logger.error("Error executing pre-request script", e);
            e.printStackTrace();
        }
    }

    private void executePostRequestScript(ApiRequestDTO apiRequest, ApiResponse response) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");
        engine.put("console", new Console());
        engine.put("globals", globals);
        engine.put("variables", variables);
        engine.put("request", apiRequest);
        engine.put("response", response);
        try {
            engine.eval(apiRequest.getPostRequestScript());
        } catch (ScriptException e) {
            logger.error("Error executing post-request script", e);
            e.printStackTrace();
        }
    }

    private ApiRequestDTO replacePlaceHoldersWithVariables(ApiRequestDTO apiRequest) {
        Map<String, String> mergedVariables = new HashMap<>();

        // Merge globals and variables
        if (globals != null) {
            mergedVariables.putAll(globals);
        }
        if (variables != null) {
            mergedVariables.putAll(variables);
        }

        for (Map.Entry<String, String> entry : mergedVariables.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            String placeholder = "\\{\\{" + key + "\\}\\}"; // Escape braces for regex

            apiRequest.setPath(apiRequest.getPath().replaceAll(placeholder, value));

            if (apiRequest.getRequestBody() != null) {
                apiRequest.setRequestBody(apiRequest.getRequestBody().replaceAll(placeholder, value));
            }

            if (apiRequest.getHeaders() != null) {
                apiRequest.setHeaders(replaceInList(apiRequest.getHeaders(), placeholder, value));
            }

            if (apiRequest.getQueryParams() != null) {
                apiRequest.setQueryParams(replaceInList(apiRequest.getQueryParams(), placeholder, value));
            }
        }

        return apiRequest;
    }

    private List<ApiRequestKeyValueDTO> replaceInList(List<ApiRequestKeyValueDTO> originalList, String placeholder, String value) {
        List<ApiRequestKeyValueDTO> replacedList = new ArrayList<>();
        Pattern pattern = Pattern.compile(placeholder);

        originalList.forEach(kv -> {
            String replacedKey = pattern.matcher(kv.getKey()).replaceAll(value);
            String replacedValue = pattern.matcher(kv.getValue()).replaceAll(value);
            replacedList.add(new ApiRequestKeyValueDTO(kv.isEnabled(), replacedKey, replacedValue));
        });

        return replacedList;
    }


}
