/**
 * Convert Postman header array format to Bruno headers object
 * @param {Object} j - jscodeshift API
 * @param {Object} arrayValue - Array expression of key-value pair objects
 * @returns {Object} - Object expression with key-value pairs
 */
const convertArrayToObject = (j, arrayValue) => {
  const obj = j.objectExpression([]);
  
  if (arrayValue.type === 'ArrayExpression') {
    arrayValue.elements.forEach(elem => {
      if (elem.type === 'ObjectExpression') {
        const keyProp = elem.properties.find(p => (p.key.name === 'key' || p.key.value === 'key'));
        const valueProp = elem.properties.find(p => (p.key.name === 'value' || p.key.value === 'value'));

        if (keyProp && valueProp) {
          obj.properties.push(
            j.property(
              'init',
              j.literal(keyProp.value.value),
              valueProp.value
            )
          );
        }
      }
    });
  }
  
  return obj;
};

/**
 * Add or update a specific header in the request options
 * @param {Object} j - jscodeshift API
 * @param {Object} requestOptions - Request options object
 * @param {string} headerName - Header name to add/update
 * @param {string} headerValue - Header value
 */
const addOrUpdateHeader = (j, requestOptions, headerName, headerValue) => {
  let headersProp = requestOptions.properties.find(p => (p.key.name === 'headers' || p.key.value === 'headers'));

  if (!headersProp) {
    headersProp = j.property('init', j.identifier('headers'), j.objectExpression([]));
    requestOptions.properties.push(headersProp);
  } else if (headersProp.value.type !== 'ObjectExpression') {
    headersProp.value = j.objectExpression([]);
  }

  // filter out existing header with same name (case-insensitive)
  headersProp.value.properties = headersProp.value.properties.filter(p =>
    p.key.type !== 'Literal' ||
    p.key.value.toLowerCase() !== headerName.toLowerCase()
  );

  headersProp.value.properties.push(
    j.property(
      'init',
      j.literal(headerName),
      j.literal(headerValue)
    )
  );
};

/**
 * Transform headers property from array to object format
 * @param {Object} j - jscodeshift API
 * @param {Object} requestOptions - Request options object
 */
const transformHeaders = (j, requestOptions) => {
  if (requestOptions.type !== 'ObjectExpression') return;

  requestOptions.properties.forEach(prop => {
    // find and rename 'header' property to 'headers'
    if (prop.key.name === 'header' || prop.key.value === 'header') {
      prop.key.name = 'headers';
      prop.key.value = 'headers';

      // Handle array of header objects
      if (prop.value.type === 'ArrayExpression') {
        prop.value = convertArrayToObject(j, prop.value);
      }
    }
  });
};

/**
 * Transform body property based on body mode
 * @param {Object} j - jscodeshift API
 * @param {Object} requestOptions - Request options object
 * @returns {Array|null} - Array of statements if formdata is used, null otherwise
 */
const transformBody = (j, requestOptions) => {
  if (requestOptions.type !== 'ObjectExpression') return null;
  
  requestOptions.properties.forEach(prop => {
    if (prop.key.name === 'body' || prop.key.value === 'body') {
      if (prop.value.type === 'ObjectExpression') {
        const bodyProps = prop.value.properties;
        const modeProp = bodyProps.find(p => (p.key.name === 'mode' || p.key.value === 'mode'));

        if (modeProp && modeProp.value.type === 'Literal') {
          const bodyMode = modeProp.value.value;

          // Handle raw mode (text, json, xml, etc.)
          if (bodyMode === 'raw') {
            const rawProp = bodyProps.find(p => (p.key.name === 'raw' || p.key.value === 'raw'));

            if (rawProp) {
              // Replace body with data
              prop.key.name = 'data';
              prop.key.value = 'data';
              prop.value = rawProp.value;
            }
          }
          // Handle urlencoded mode
          else if (bodyMode === 'urlencoded') {
            const urlencodedProp = bodyProps.find(p => (p.key.name === 'urlencoded' || p.key.value === 'urlencoded') && p.value.type === 'ArrayExpression');

            if (urlencodedProp) {
              // Replace the body property with a 'data' property
              prop.key.name = 'data';
              prop.key.value = 'data';
              
              // Transform the urlencoded array to an object
              prop.value = convertArrayToObject(j, urlencodedProp.value);

              // Add Content-Type header for urlencoded
              addOrUpdateHeader(j, requestOptions, 'Content-Type', 'application/x-www-form-urlencoded');
            }
          }
          // Handle formdata mode
          else if (bodyMode === 'formdata') {
            const formdataProp = bodyProps.find(p => (p.key.name === 'formdata' || p.key.value === 'formdata') && p.value.type === 'ArrayExpression');

            if (formdataProp) {
                // Replace the body property with a 'data' property
                prop.key.name = 'data';
                prop.key.value = 'data';
                
                // Transform the urlencoded array to an object
                prop.value = convertArrayToObject(j, formdataProp.value);
  
                // Add Content-Type header for urlencoded
                addOrUpdateHeader(j, requestOptions, 'Content-Type', 'multipart/form-data');
            }
          }
        }
      }
    }
  });
};

/**
 * Transform callback function to Bruno format
 * @param {Object} j - jscodeshift API
 * @param {Object} callback - Callback function expression
 * @returns {Object} - Transformed callback function
 */
const transformCallback = (j, callback) => {
  if (!callback || (callback.type !== 'FunctionExpression' && callback.type !== 'ArrowFunctionExpression')) return null;
  
  const params = callback.params;
  const callbackBody = callback.body;

  // Get the response parameter name (typically the second param)
  let responseVarName = 'response'; // Default if not found
  if (params.length >= 2 && params[1].type === 'Identifier') {
    responseVarName = params[1].name;
  }

  let errorVarName = 'error'; // Default if not found
  if (params.length >= 1 && params[0].type === 'Identifier') {
    errorVarName = params[0].name;
  }

  // Define translations for callback response properties
  const responsePropertyMap = {
    'json': 'data',
    'text': 'data', 
    'code': 'status',
    'status': 'statusText',
  };

  // Process the callback body to transform response property references
  j(callbackBody).find(j.MemberExpression, {
    object: {
      type: 'Identifier',
      name: responseVarName
    }
  }).forEach(memberPath => {
    const property = memberPath.node.property;

    // Handle property access
    if (property.type === 'Identifier' && responsePropertyMap[property.name]) {
      const bruProperty = responsePropertyMap[property.name];
      if (bruProperty) {
        // Check if memberPath is part of a CallExpression
        const parentPath = memberPath.parent;
        if (parentPath && parentPath.node.type === 'CallExpression') {
          // Replace the entire CallExpression with a property access
          j(parentPath).replaceWith(
            j.memberExpression(
              j.identifier(responseVarName),
              j.identifier(bruProperty)
            )
          );
        } else {
          // Regular property access replacement
          j(memberPath).replaceWith(
            j.memberExpression(
              j.identifier(responseVarName),
              j.identifier(bruProperty)
            )
          );
        }
      }
    }
  });

  // Create the callback block
  return j.functionExpression(
    null,
    [j.identifier(errorVarName), j.identifier(responseVarName)],
    j.blockStatement(callbackBody.body)
  );
};

const sendRequestTransformer = (path, j) => {
  const callExpr = path.parent.value;
  if (callExpr.type !== 'CallExpression') return;

  // Clone the argument object for modification
  const args = [...callExpr.arguments];
  if (!args.length) return;

  const requestOptions = args[0];
  const callback = args[1];

  // Check if original call was awaited
  const wasAwaited = path.parent.parent.value.type === 'AwaitExpression';

  // transform the request config options
  if (requestOptions.type === 'ObjectExpression') {
    // Transform headers
    transformHeaders(j, requestOptions);
    // Transform body
    transformBody(j, requestOptions);
  }

  // Create the callback block and promise chain if there's a callback
  if (callback) {
    const transformedCallback = transformCallback(j, callback);
    
    // Add async keyword to the callback function
    if (transformedCallback && (transformedCallback.type === 'FunctionExpression' || transformedCallback.type === 'ArrowFunctionExpression')) {
      transformedCallback.async = true;
    }
    
    // Create expression: await bru.sendRequest(requestConfig, callback);
    const sendRequestCall = j.callExpression(
      j.identifier('bru.sendRequest'),
      transformedCallback ? [requestOptions, transformedCallback] : [requestOptions]
    );

    return wasAwaited ? sendRequestCall : j.awaitExpression(sendRequestCall);
  }

  // If there's no callback, just transform to await bru.sendRequest
  const sendRequestCall = j.callExpression(
    j.identifier('bru.sendRequest'),
    [requestOptions]
  );
  
  return wasAwaited ? sendRequestCall : j.awaitExpression(sendRequestCall);
};

export default sendRequestTransformer;