package com.eactive.testmaster.api.controller;

import com.eactive.testmaster.api.condition.ConditionMatcher;
import com.eactive.testmaster.api.condition.ConditionMatcherService;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import com.eactive.testmaster.api.entity.MockResponse;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.api.service.MockRouteService;
import com.eactive.testmaster.config.HazelcastJSBridge;
import com.eactive.testmaster.loadtest.service.Console;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MockApiController {

    private final MockRouteService mockRouteService;
    private final HazelcastJSBridge hazelcastJSBridge;
    private final ConditionMatcherService conditionMatcherService;
    private final ScriptEngine scriptEngine;  // Add ScriptEngine as a field
    private final Console console;  // Add Console as a field

    private static final Logger logger = LoggerFactory.getLogger(MockApiController.class);


    public MockApiController(MockRouteService mockRouteService, HazelcastJSBridge hazelcastJSBridge, ConditionMatcherService conditionMatcherService) {
        this.mockRouteService = mockRouteService;
        this.hazelcastJSBridge = hazelcastJSBridge;
        this.conditionMatcherService = conditionMatcherService;
        // Initialize ScriptEngine once
        ScriptEngineManager manager = new ScriptEngineManager();
        this.scriptEngine = manager.getEngineByName("nashorn");
        this.hazelcastJSBridge.enhanceEngine(this.scriptEngine);

        // Initialize Console once
        this.console = new Console();
    }


    private void executeScript(String script, MockRequest httpRequest) {
        if (script != null && !script.isEmpty()) {
            Bindings scriptBindings = new SimpleBindings();
            scriptBindings.put("request", httpRequest);
            scriptBindings.put("console", this.console);
            try {
                scriptEngine.eval(script, scriptBindings);
            } catch (ScriptException e) {
                logger.error("Error executing request script", e);
                e.printStackTrace();
            }
        }
    }

    public ResponseEntity<String> handleRequest(HttpServletRequest httpRequest) throws IOException {

        MockRequest request = new MockRequest(httpRequest);
        AntPathMatcher pathMatcher = new AntPathMatcher();
        logger.debug("===========================");
        logger.debug("Request: " + request.getMethod() + " " + request.getUri());
        logger.debug("Headers:" + request.getHeaders().toString());
        logger.debug("Params: " + request.getParams().toString());
        logger.debug("Body: (URL Decoded)");
        logger.debug(request.getBody());
        logger.debug("===========================");

        for (MockRoute mockRoute : mockRouteService.getAllRoutes()) {
            if (isRouteMatch(mockRoute, request)) {
                if (mockRoute.getRequestScript() != null && !mockRoute.getRequestScript().isEmpty()) {
                    executeScript(mockRoute.getRequestScript(), request);
                }

                Map<String, String> pathVariables = pathMatcher.extractUriTemplateVariables(mockRoute.getPathPattern(), request.getUri());
                MockResponse matchingResponse = findMatchingResponse(mockRoute, request, pathVariables);

                if (matchingResponse != null) {
                    if (matchingResponse.getDelay() > 0) {
                        try {
                            Thread.sleep(matchingResponse.getDelay());
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            logger.error("Error while delaying response", e);
                            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing request due to interruption.");
                        }
                    }

                    executeScript(matchingResponse.getResponseScript(), request);

                    HttpHeaders responseHeaders = new HttpHeaders();
                    matchingResponse.getHeaders().forEach(responseHeaders::set);

                    String encodingCharset = StringUtils.isNotEmpty(matchingResponse.getEncodingCharset()) ? matchingResponse.getEncodingCharset() : System.getProperty("file.encoding");

                    if (!responseHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
                        responseHeaders.add(HttpHeaders.CONTENT_TYPE, "text/plain; charset=" + encodingCharset);
                    } else {
                        String contentType = responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE);
                        if (!contentType.contains("charset")) {
                            responseHeaders.set(HttpHeaders.CONTENT_TYPE, contentType + "; charset=" + encodingCharset);
                        } else {
                            if (!contentType.contains(encodingCharset)) {
                                logger.warn("Existing content type charset does not match the encodingCharset: " + encodingCharset);
                            }
                        }
                    }

                    request.getParams().putAll(pathVariables);

                    return new ResponseEntity<>(replaceParams(matchingResponse.getBody(), request.getParams(), encodingCharset), responseHeaders, HttpStatus.valueOf(matchingResponse.getStatusCode()));
                }
            }
        }

        // If no matching route or response is found, return a 404 Not Found
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Not Found");
    }

    private String replaceParams(String responseBody, Map<String, String> params, String encodingCharset) throws UnsupportedEncodingException {
        for (Map.Entry<String, String> entry : params.entrySet()) {
            responseBody = responseBody.replace("{" + entry.getKey() + "}", entry.getValue());
        }

        byte[] sourceBytes = responseBody.getBytes(encodingCharset);
        return new String(sourceBytes, encodingCharset);
    }

    private boolean isRouteMatch(MockRoute mockRoute, MockRequest request) {
        AntPathMatcher pathMatcher = new AntPathMatcher();
        return mockRoute.getMethod().equalsIgnoreCase(request.getMethod()) && pathMatcher.match(mockRoute.getPathPattern(), request.getUri());
    }

    private MockResponse findMatchingResponse(MockRoute mockRoute, MockRequest mockRequest, Map<String, String> pathVariables) {

        MockResponse defaultResponse = mockRoute.getResponses().stream().filter(MockResponse::isDefaultResponse).findFirst().orElse(null);
        List<MockResponse> responses = mockRoute.getResponses().stream().filter(response -> !response.isDefaultResponse()).collect(Collectors.toList());
        if (defaultResponse != null) {
            responses.add(defaultResponse);
        }

        for (MockResponse response : responses) {
            if (response.getConditions() == null || isRequestMatching(response.getConditions(), mockRequest, pathVariables)) {
                return response;
            }
        }
        return null;
    }


    private boolean isRequestMatching(List<MockCondition> conditions, MockRequest request, Map<String, String> pathVariables) {

        if (conditions == null) {
            return true;
        }

        for (MockCondition condition : conditions) {
            ConditionMatcher matcher = conditionMatcherService.getMatcher(condition.getType().toLowerCase());
            if (matcher != null && !matcher.matches(condition, request, pathVariables)) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        String encodedString = "=%7B%22STDD_HEADER%22%3A%7B%22HEADER%22%3A%7B%22STDD_EMSG_LEN%22%3A%22%22%2C%22ECRY_CMPR_DV_CD%22%3A%22%22%2C%22GUID%22%3A%221%22%2C%22EMSG_REQ_DV_CD%22%3A%22S%22%2C%22EMSG_VER_NO%22%3A%22%22%2C%22FRST_DLV_IP%22%3A%22%22%2C%22TML_MAC_ADDR%22%3A%22%22%2C%22FRST_DLV_SYS_CD%22%3A%22%22%2C%22DLV_SYS_CD%22%3A%22%22%2C%22DLV_NODE_NO%22%3A%22%22%2C%22MCI_SSN_ID%22%3A%22%22%2C%22HDR_PRLM_CTNT%22%3A%22%22%7D%2C%22COMMON%22%3A%7B%22SCR_NO%22%3A%22%22%2C%22SVC_ID%22%3A%22SFW010014ZR%22%2C%22PCS_RESLT_DP_SVC_ID%22%3A%22%22%2C%22SYS_INTF_ID%22%3A%22%22%2C%22TS_SYNC_DV_CD%22%3A%22A%22%2C%22FIOS_DV_CD%22%3A%222%22%2C%22OTSD_CHN_ORG_CD%22%3A%22%22%2C%22SYS_OP_ENVR_DV_CD%22%3A%22%22%2C%22TS_TYP_DV_CD%22%3A%22%22%2C%22TS_TYP_DT_DV_CD%22%3A%22%22%2C%22ORTS_RESTOR_YN%22%3A%22%22%2C%22ORTS_GUID%22%3A%22%22%2C%22DPT_CD_V4%22%3A%22%22%2C%22PTCP_ORG_CD%22%3A%22%22%2C%22TML_NO%22%3A%22%22%2C%22USER_ID_V8%22%3A%22%22%2C%22BSNS_YMD%22%3A%22%22%2C%22SYS_YMD%22%3A%22%22%2C%22SYS_HMS_V8%22%3A%22%22%2C%22EMSG_REQ_YMD%22%3A%22%22%2C%22EMSG_REQ_TIME%22%3A%22%22%2C%22PCS_RESLT_DV_CD%22%3A%22%22%2C%22PRT_EMSG_TYP_CD%22%3A%22%22%2C%22LRQN_PRT_HALT_TGT_YN%22%3A%22%22%2C%22EMSG_CNTNTN_SEQ%22%3A%22%22%2C%22DTYR_APRV_PCS_STT_CD%22%3A%22%22%2C%22DTYR_APRV_DLR_DV_CD%22%3A%22%22%2C%22CLSNG_KIND_CD%22%3A%22%22%2C%22DTYR_TS_SEQ%22%3A%22%22%2C%22APRV_CNNR_ID%22%3A%22%22%2C%22DTYR_PWD%22%3A%22%22%2C%22OTP_SEQ%22%3A%22%22%2C%22OTP_CERT_NO%22%3A%22%22%2C%22ERR_CD_V9%22%3A%22%22%2C%22ERR_SYS_CD%22%3A%22%22%2C%22MST_SCR_NO%22%3A%22%22%2C%22COM_PRLM_CTNT%22%3A%22%22%7D%7D%2C%22STDD_DATA%22%3A%7B%22HEADER%22%3A%7B%22DATA_HDR_KIND_CD%22%3A%22%22%2C%22DATA_HDR_LEN%22%3A%22%22%7D%2C%22DATA%22%3A%7B%22FICdtTrf%22%3A%7B%22GrpHdr%22%3A%7B%22MsgId%22%3A%22FFFF%2F151022-SEK%2F110%22%2C%22CreDtTm%22%3A%222015-10-22T10%3A11%3A00%2B09%3A00%22%2C%22NbOfTxs%22%3A%221%22%2C%22SttlmInf%22%3A%7B%22SttlmMtd%22%3A%22CLRG%22%2C%22ClrSys%22%3A%7B%22Cd%22%3A%22BOK%22%7D%7D%7D%2C%22CdtTrfTxInf%22%3A%7B%22PmtId%22%3A%7B%22InstrId%22%3A%22FFFF%2F151022-SEK%2F110%2F1%22%2C%22EndToEndId%22%3A%22BBBBTT4148AAAAPP%22%2C%22UETR%22%3A%22f47ac10b-58cc-4372-a567-0e02b2c3d479%22%7D%2C%22PmtTpInf%22%3A%7B%22InstrPrty%22%3Anull%2C%22SvcLvl%22%3A%7B%22Cd%22%3A%22Cd%22%2C%22Prtry%22%3Anull%7D%7D%2C%22IntrBkSttlmAmt%22%3Anull%2C%22IntrBkSttlmDt%22%3A%222001-01-01%22%2C%22InstgAgt%22%3A%7B%22FinInstnId%22%3A%7B%22BICFI%22%3Anull%2C%22ClrSysMmbId%22%3A%7B%22ClrSysId%22%3A%7B%22Cd%22%3A%22KRBOK%22%7D%2C%22MmbId%22%3A%22MmbId%22%7D%2C%22LEI%22%3Anull%7D%7D%2C%22Dbtr%22%3A%7B%22FinInstnId%22%3A%7B%22BICFI%22%3Anull%2C%22ClrSysMmbId%22%3A%7B%22ClrSysId%22%3A%7B%22Cd%22%3A%22KRBOK%22%7D%2C%22MmbId%22%3A%22MmbId%22%7D%7D%7D%2C%22Cdtr%22%3A%5B%7B%22FinInstnId%22%3A%7B%22BICFI%22%3Anull%2C%22ClrSysMmbId%22%3A%7B%22ClrSysId%22%3A%7B%22Cd%22%3A%22KRBOK%22%7D%2C%22MmbId%22%3A%22MmbId%22%7D%7D%7D%2C%7B%22FinInstnId%22%3A%7B%22BICFI%22%3Anull%2C%22ClrSysMmbId%22%3A%7B%22ClrSysId%22%3A%7B%22Cd%22%3A%22KRBOK1%22%7D%2C%22MmbId%22%3A%22MmbId1%22%7D%7D%7D%5D%7D%7D%7D%7D%7D";

        try {
//            String decodedString = URLDecoder.decode(encodedString, StandardCharsets.UTF_8.name());
            String decodedString = URLDecoder.decode(encodedString);
            System.out.println("Decoded string: " + decodedString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
