/**
 * 처리 순서
 * 1. preRequestScript 실행 -> executePreRequestScript
 * 2. 변수 치환 -> replacePlaceholdersWithVariables
 * 3. API 호출 -> sendAPIRequest
 * 4. postRequestScript 실행 -> executePostRequestScript
 */

class APIClient {

  CLIENT_URL = 'mgmt/api/test.do';

  constructor() {
    window.globals = {};
    this.abortController = null;
    this.contextPath = document.querySelector('meta[name="context-path"]').getAttribute('content');

    if (!this.contextPath) {
      this.contextPath = '/';
    }

    if (!this.contextPath.endsWith('/')) {
      this.contextPath += '/';
    }
  }

  getClientUrl() {
    return this.contextPath + this.CLIENT_URL;
  }

  formatResponse(data) {
    // Format response as needed before post-request script
    let response = {};
    response.body = data.body;
    response.status = data.status;
    response.headers = {};
    response.size = data.size;
    _.forEach(data.headers, (header) => {
      response.headers[header.key] = header.value;
    });
    return response;
  }

  abort() {
    if (this.abortController) {
      this.abortController.abort(); // abort the fetch
      this.abortController = null; // reset the controller
    }
  }

  makeAjaxRequest(processedRequest, model) {
    this.abortController = new AbortController();
    const {signal} = this.abortController;

    try {
      return fetch(this.getClientUrl(), {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
        },
        body: JSON.stringify(processedRequest),
        signal: signal // pass the signal to the fetch
      });
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('Network request failed:', error);
      }
      throw error;
    }
  }

  async processPreScriptAndVariables(model, value,  consoleOutput){
    let variables = {};
    const updatedModel = await this.executePreRequestScript(model, variables, consoleOutput);
    return this.replacePlaceholderValueWithVariables(updatedModel, value, variables);
  }

  replacePlaceholderValueWithVariables(model, originalValue, variables) {
    let mergedVariables = {};
    _.forEach(window.globals, (value, key) => {
      mergedVariables[key] = value;
    });

    if (variables) {
      _.forEach(variables, (value, key) => {
        mergedVariables[key] = value;
      });
    }

    _.forEach(mergedVariables, (value, key) => {
      const placeholder = `{{${key}}}`;
      // Replace in path
      originalValue = originalValue.replace(new RegExp(placeholder, 'g'), value);
    });
    return originalValue;
  }

  async sendAPIRequest(model, preProcessCallback, consoleOutput) {
    try {
      let variables = {};
      const updatedModel = await this.executePreRequestScript(model, variables, consoleOutput);
      let processedRequest = this.replacePlaceholdersWithVariables(updatedModel, variables);

      if (preProcessCallback) {
        preProcessCallback(processedRequest);
      }
      const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput);
      const responseData = await response.json(); // Parse the JSON response

      if (!response.ok) {
        const errorMessage = responseData.error || 'Unknown error';
        throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`);
      }

      const finalResponse = this.formatResponse(responseData);
      return this.executePostRequestScript(model.postRequestScript, variables, processedRequest, finalResponse, consoleOutput).catch(error => {
        $('.toast-body').text(error);
        $('.toast').toast('show');
        return finalResponse; // Return the original response even if there's an error in the post-request script
      });

    } catch (error) {
      throw error;
    }
  }

  replacePlaceholdersWithVariables(original, variables) {
    let request = JSON.parse(JSON.stringify(original));

    let mergedVariables = {};
    _.forEach(window.globals, (value, key) => {
      mergedVariables[key] = value;
    });

    if (variables) {
      _.forEach(variables, (value, key) => {
        mergedVariables[key] = value;
      });
    }

    _.forEach(mergedVariables, (value, key) => {
      const placeholder = `{{${key}}}`;
      // Replace in path
      request.path = request.path.replace(new RegExp(placeholder, 'g'), value);

      // Replace in requestBody
      if (request.requestBody) {
        request.requestBody = request.requestBody.replace(new RegExp(placeholder, 'g'), value);
      }

      // Replace in headers
      if (request.headers) {
        let replaced = JSON.stringify(request.headers).replace(new RegExp(placeholder, 'g'), value);
        request.headers = JSON.parse(replaced);
      }

      // Replace in queryParams
      if (request.queryParams) {
        let replaced = JSON.stringify(request.queryParams).replace(new RegExp(placeholder, 'g'), value);
        request.queryParams = JSON.parse(replaced);
      }
    });

    return request;
  }

  executePreRequestScript(model, variables, consoleOutput) {
    return new Promise((resolve, reject) => {
      let script = model.preRequestScript;
      let resultFrame = document.getElementById('preRequest');



      window.preRequestComplete = (updatedModel, error) => {
        if (error) {
          reject(error);
        } else {
          resolve(updatedModel);
        }
      };

      window.temp_variable = variables;
      window.currentRequest = model;
      window.consoleOutput = consoleOutput;
      resultFrame.srcdoc = `<script>
                var oldLog = console.log;
                console.log = function(message) {
                    if(window.parent.consoleOutput)
                        window.parent.consoleOutput(message);  
                    oldLog.apply(console, arguments);
                };
                
                window.globals = window.parent.globals;
                window.variables = window.parent.temp_variable;
                window.request = window.parent.currentRequest;
            
                try {
                    ${script}
                    window.parent.preRequestComplete(window.request, null);
                } catch (error) { 
                    window.parent.preRequestComplete(null, error);
                }
            </script>`;
    });
  }

  executePostRequestScript(script, variables, request, response, consoleOutput) {
    return new Promise((resolve, reject) => {
      let resultFrame = document.getElementById('postRequest');

      window.request = request;
      window.response = response;
      window.temp_variable = variables;
      window.postRequestComplete = (response, error) => {
        if (error) {
          reject(error);
        } else {
          resolve(response);
        }
      };
      window.consoleOutput = consoleOutput;
      resultFrame.srcdoc = `<script>
            var oldLog = console.log;
            console.log = function(message) {
                if(window.parent.consoleOutput)
                    window.parent.consoleOutput(message); 
                oldLog.apply(console, arguments);
            };
            window.globals = window.parent.globals;
            window.variables = window.parent.temp_variable;
            window.request = window.parent.request;
            window.response = window.parent.response;
            
            try {
                ${script} 
                window.parent.postRequestComplete(window.response, null); 
            } catch (error) {
                window.parent.postRequestComplete(window.response, error.toString());
            }
        </script>`;
    });
  }
}

export default APIClient;
