import AioTestsLogger from "./AioTestsLogger";
import {
  AioConfig,
  BulkRequestBody,
  CreateCycleBody,
  CustomFieldUpdate,
  CustomFieldUpdateData,
  TestData,
  TestDataEntry,
} from "./model";
import axios, { AxiosInstance, AxiosResponse, isAxiosError} from "axios";
import fs from "fs";
import FormData from "form-data";
import { TestCase, TestResult } from "@playwright/test/reporter";

const baseUrl = "https://tcms.aiojiraapps.com/aio-tcms/api/v1";
const restVersion = "/rest/aio-tcms-api/1.0";
const rateLimitWaitTime = 60 * 1000;
const apiTimeout = 45 * 1000;
const CREATE_IF_ABSENT = "CREATE_IF_ABSENT";
const createNewCycleOptions = [true, false, "true", "false", CREATE_IF_ABSENT];
const bulkRequestBody: BulkRequestBody = { testRuns: [] };
const browserKeyMap: Map<string, string[]> = new Map();
const browserBulkRequestMap: Map<String, BulkRequestBody> = new Map();

let debugMode = false;
let aioAPIClient: AxiosInstance | null;
let isAttachmentAPIAvailable: boolean | null = null;
let stopReporting: boolean = false;
let allCaseKeys: string[] = [];
let allFailedRuns: string[] = [];
let bulkTestRunUpdate = false;
let multiBrowser = false;

async function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function initAPIClient(aioConfig: AioConfig) {
  debugMode = !!aioConfig.debugMode;
  AioTestsLogger.setDebugMode(debugMode);
  AioTestsLogger.debug("Debug Mode is set to true");
  if (aioConfig.cloud || process.env.AIO_API_KEY) {
    if (!aioConfig.cloud.apiKey && !process.env.AIO_API_KEY) {
      AioTestsLogger.error(
        'Cloud hosting config does not specify apiKey.  Please add "cloud":{"apiKey":"auth token"}',
        debugMode
      );
    } else {
      let apiKey = process.env.AIO_API_KEY
        ? process.env.AIO_API_KEY
        : aioConfig.cloud.apiKey;
      aioAPIClient = axios.create({
        baseURL: baseUrl,
        timeout: apiTimeout,
        headers: { Authorization: `AioAuth ${apiKey}` },
      });
    }
    return;
  } else if (aioConfig.hosted) {
    if (!!!aioConfig.hosted.jiraUrl) {
      AioTestsLogger.error(
        'Server hosting config does not specify jiraUrl.  Please add "server":{"jiraUrl":"https://companyhostedjira.com/jira"}'
      );
    } else {
      aioAPIClient = axios.create({
        baseURL: aioConfig.hosted.jiraUrl + restVersion,
        timeout: apiTimeout,
      });
      if (aioConfig.hosted.jiraUsername || process.env.JIRA_USERNAME) {
        let jUsername = process.env.JIRA_USERNAME
          ? process.env.JIRA_USERNAME
          : aioConfig.hosted.jiraUsername;
        let jPassword = process.env.JIRA_PASSWORD
          ? process.env.JIRA_PASSWORD
          : aioConfig.hosted.jiraPassword;
        aioAPIClient.defaults.auth = {
          username: jUsername,
          password: jPassword,
        };
      } else if (aioConfig.hosted.jiraPAT || process.env.JIRA_PAT) {
        let jPAT = process.env.JIRA_PAT
          ? process.env.JIRA_PAT
          : aioConfig.hosted.jiraPAT;
        aioAPIClient.defaults.headers.common[
          "Authorization"
        ] = `Bearer ${jPAT}`;
      } else {
        AioTestsLogger.error(
          "Server hosting config missing Jira username or PAT. " +
            ' Please set JIRA_USERNAME/JIRA_PASSWORD or JIRA_PAT as an environment variable or add "hosted":{"jiraUrl":"yoururl", "jiraUsername":"un", "jiraPassword":"pwd"}' +
            ' or "hosted":{"jiraUrl":"your url", "jiraPAT":"PAT token"} to config file'
        );
        aioAPIClient = null;
      }
    }
    return;
  }
  AioTestsLogger.error(
    "No authentication information found.  Please set AIO_API_KEY/JIRA_USERNAME/JIRA_PAT as an environment variable or " +
      'add "cloud":{"apiKey":"auth token"} or "server":{"jiraServerUrl":"val"}) to config'
  );
}

export const validateAIOConfig = (config: AioConfig, reportError: boolean): AioConfig | undefined => {
  if (!!config.enableReporting) {
    if (!!!config.jiraProjectId && !!reportError) {
      AioTestsLogger.error(
        "Jira Project Id is mandatory for AIO Tests Reporting.",
        true
      );
      return;
    }
    initAPIClient(config);
    if (!aioAPIClient) {
      AioTestsLogger.error(
        "Please specify valid credentials to connect with AIO Tests."
      );
      return;
    }
    return config;
  } else {
    if (reportError)
      AioTestsLogger.error(
        "AIO Tests reporting is not enabled.  Please set env:{aioTests:{enableReporting:true}}",
        true
      );
  }
  return undefined;
};

const getOrCreateCycle = async (aioConfig: AioConfig): Promise<string | void> => {
  if (!aioAPIClient) {
    return Promise.resolve();
  }
  AioTestsLogger.logStartEnd("Determining cycle to update");
  if (!aioConfig.cycleDetails) {
    AioTestsLogger.error(
      'Please specify cycleDetails in config.  eg. "cycleDetails": {"cycleKey":"AT-CY-11"}',
      true
    );
    return Promise.resolve();
  } else {
    let aioCycleConfig = aioConfig.cycleDetails;
    if (
      aioCycleConfig.createNewCycle &&
      !createNewCycleOptions.includes(aioCycleConfig.createNewCycle)
    ) {
      return Promise.resolve(
        `Invalid value for createNewCycle "${aioCycleConfig.createNewCycle}". Valid values include ${createNewCycleOptions}.`
      );
    }
    if ((!aioCycleConfig.createNewCycle || "false" === aioCycleConfig.createNewCycle) && aioCycleConfig.cycleKey) {
      aioCycleConfig["cycleKeyToReportTo"] = aioCycleConfig.cycleKey;
      return Promise.resolve();
    }

    if (aioCycleConfig.createNewCycle === true || "true" === aioCycleConfig.createNewCycle || aioCycleConfig.createNewCycle === CREATE_IF_ABSENT) {
      let cycleTitle = aioCycleConfig.cycleName;
      let customFields = aioCycleConfig.customFields;
      if (!cycleTitle) {
        return Promise.resolve("createNewCycle is set to " + aioCycleConfig.createNewCycle + " in config.  Please set cycleName.");
      }
      if (aioCycleConfig.createNewCycle === CREATE_IF_ABSENT) {
        let results: any = await findExistingCycleThroughName(aioConfig);
        if (results === true) {
          AioTestsLogger.debug("Existing cycle found");
          //Cycle found and set.
          return;
        }
        if (results !== false) {
          //Error while finding cycle.
          AioTestsLogger.log(results);
          return;
        }
      }

      AioTestsLogger.log("Creating cycle : " + cycleTitle);
      let folderCreationPromise = getOrCreateFolder(
        aioConfig.jiraProjectId,
        aioCycleConfig
      );
      return folderCreationPromise
        .then(async (folderCreationResponse) => {
          AioTestsLogger.debug("Folder task resolved.  Creating cycle.");
          let createCycleBody: CreateCycleBody = {
            title: cycleTitle,
            customFields: customFields || null,
          };
          if (folderCreationResponse) {
            createCycleBody.folder = folderCreationResponse.data;
          }
          if (aioCycleConfig.tasks && aioCycleConfig.tasks.length > 0 && Array.isArray(aioCycleConfig.tasks)) {
            let jiraTasks = aioCycleConfig.tasks.filter((f) => f && !!f.trim());
            if (jiraTasks.length > 0) {
              createCycleBody.jiraTaskIDs = jiraTasks;
            }
          }
          AioTestsLogger.debug("Cycle endpoint " + "/project/" + aioConfig.jiraProjectId + "/testcycle/detail");
          try {
            let response = await aioAPIClient!
                .post(
                    "/project/" + aioConfig.jiraProjectId + "/testcycle/detail",
                    createCycleBody
                );
            aioCycleConfig["cycleKeyToReportTo"] = response.data.key;
            AioTestsLogger.log("Cycle created successfully : " + aioCycleConfig.cycleKeyToReportTo);
          } catch (error: any) {
            debugLogError(error);
            if (error.response) {
              if (
                  error.response.status === 401 ||
                  error.response.status === 403
              ) {
                return Promise.resolve(
                    "Authorization error.  Please check credentials."
                );
              } else {
                return Promise.resolve(
                    error.response.status + " : " + error.response.data
                );
              }
            }
          }
        })
        .catch((error: any) => {
          debugLogError(error);
          if (error.response) {
            AioTestsLogger.error(
              error.response.status + " : " + error.response.data
            );
          }
          return Promise.resolve(
            "Error in fetching or creating cycle folder.  " +
              'Please check format of folder, for eg. ["Cloud","Release1"]'
          );
        });
    }

    return Promise.resolve(
      'createNewCycle is false in config.  Please specify a cycle key (eg. AT-CY-11) as "cycleKey":"AT-CY=11" or cycle name as "cycleName":"Regression release 1" '
    );
  }
};

async function getOrCreateFolder(jiraProjectId: string, aioCycleConfig: { folder?: string[] }): Promise<any> {
  if (aioCycleConfig.folder && Array.isArray(aioCycleConfig.folder) && aioCycleConfig.folder.length > 0) {
    const userFolderHierarchy = aioCycleConfig.folder.filter(
      (folder) => folder && folder.trim()
    );

    if (userFolderHierarchy.length > 0) {
      AioTestsLogger.log(`Creating or fetching folder: ${userFolderHierarchy}`);

      return aioAPIClient!.put(
        `/project/${jiraProjectId}/testcycle/folder/hierarchy`,
        {
          folderHierarchy: userFolderHierarchy,
        }
      );
    }
  } else {
    AioTestsLogger.debug("No cycle folder information set.");
  }
  return Promise.resolve();
}

async function findExistingCycleThroughName(aioConfig: AioConfig): Promise<boolean | string> {
  if (aioConfig.parallelBuild && aioConfig.parallelBuild.masterBuild === false) {
    let to = aioConfig.parallelBuild.waitForSeconds
      ? aioConfig.parallelBuild.waitForSeconds
      : 2;
    AioTestsLogger.log(`Waiting for ${to} seconds for master build to finish`);
    await new Promise((resolve) => setTimeout(resolve, to * 1000));
  }
  let body = {
    title: {
      comparisonType: "EXACT_MATCH",
      value: aioConfig.cycleDetails.cycleName!.trim(),
    },
  };
  AioTestsLogger.log("Finding cycle with name : " + aioConfig.cycleDetails.cycleName!.trim());
  return aioAPIClient!
    .post(`/project/${aioConfig.jiraProjectId}/testcycle/search`, body)
    .then(function (response) {
      const items = response?.data?.items;
      if (items && items.length > 0) {
        aioConfig.cycleDetails["cycleKeyToReportTo"] = items[0]?.key;
        return true;
      } else {
        return false;
      }
    })
    .catch(function (error) {
      debugLogError(error);
      if (error.response) {
        if (error.response.status === 401 || error.response.status === 403) {
          return Promise.resolve(
            "Authorization error.  Please check credentials."
          );
        } else {
          return Promise.resolve(
            error.response.status + " : " + error.response.data
          );
        }
      }
      return Promise.resolve(
        "An unknown error occurred while searching for the cycle."
      );
    });
}

function getCustomFieldValueToUpdate(customFieldsToUpdate: CustomFieldUpdate[]): CustomFieldUpdateData[] {
  const cfUpdates: CustomFieldUpdateData[] = [];
  if (customFieldsToUpdate) {
    customFieldsToUpdate.forEach((cf) => {
      const data: CustomFieldUpdateData = {
        customValue: { name: cf.name, value: cf.value },
      };
      if (cf.operationType) {
        data.customFieldUpdateOperationType = cf.operationType;
      }
      cfUpdates.push(data);
    });
  }
  return cfUpdates;
}

const replaceProjectNameForRunFields = (customFieldUpdates: CustomFieldUpdateData[] | null, projectName: string): CustomFieldUpdateData[] | null => {
  if (customFieldUpdates == null) {
    return null;
  }
  return customFieldUpdates.map((customFieldUpdate) =>
    replaceProjectName(customFieldUpdate, projectName)
  );
};

const replaceProjectName = (customFieldUpdate: CustomFieldUpdateData, projectName: string): CustomFieldUpdateData => {
  const { value } = customFieldUpdate.customValue;

  let newValue;

  if (typeof value === "string") {
    // Case: value is a string
    newValue = value.replace(/\$project/g, projectName);
  } else if (Array.isArray(value)) {
    if (value.every((item) => typeof item === "string")) {
      // Case: value is a string[]
      newValue = value.map((item) => item.replace(/\$project/g, projectName));
    } else if (value.every((item) => typeof item === "object" && item !== null && "value" in item)) {
      // Case: value is a { value: string }[]
      newValue = value.map((item) => ({
        value: (item as { value: string }).value.replace(/\$project/g, projectName),
      }));
    } else {
      throw new Error("Invalid array structure in value field");
    }
  } else if (typeof value === "object" && value !== null) {
    if ("value" in value) {
      // Case: value is { value: string }
      newValue = { value: value.value.replace(/\$project/g, projectName) };
    } else {
      throw new Error("Invalid object structure in value field");
    }
  } else {
    newValue = value;
  }
  return {
    customValue: { name: customFieldUpdate.customValue.name, value: newValue },
    customFieldUpdateOperationType:
      customFieldUpdate.customFieldUpdateOperationType,
  };
};

function debugLogError(error: any): void {
  if (debugMode) {
    AioTestsLogger.error("*** AIO Debug Mode Error Reporting ***");
    AioTestsLogger.errorObj(error.message);

    if (error.response) {
      const status = error.response.status;
      const data = error.response.data;
      AioTestsLogger.errorObj(`${status}  ${data}`);
    } else {
      AioTestsLogger.errorObj(error);
    }

    AioTestsLogger.error("*** AIO Debug Mode Error Reporting End ***");
  }
}

function getAIORunStatus(playwrightStatus: String): string {
  switch (playwrightStatus) {
    case "failed":
      return "Failed";
    case "passed":
      return "Passed";
    default:
      return "Not Run";
  }
}

async function uploadScreenshot(path: string|undefined, jiraProjectId: string, cyclekey: string, runId: string, trialCounter: number = 0): Promise<void> {
  if(path) {
    const form = new FormData();
    form.append("file", fs.createReadStream(path));

    try {
      await aioAPIClient!.post(
          `/project/${jiraProjectId}/testcycle/${cyclekey}/testrun/${runId}/attachment`,
          form,
          {
            headers: form.getHeaders(),
          }
      );
      AioTestsLogger.log(`Screenshot uploaded: ${path}`);
    } catch (error: any) {
      if (error.response) {
        if (error.response.status === 429 && trialCounter < 3) {
          AioTestsLogger.log("Reached AIO rate limits. Pausing...");
          await sleep(rateLimitWaitTime);
          return uploadScreenshot(
              path,
              jiraProjectId,
              cyclekey,
              runId,
              trialCounter + 1
          );
        } else if (error.response.status === 404) {
          AioTestsLogger.error(
              "Attachment API is not supported in the current API version and hence attachments could not be uploaded. Please upgrade to the latest version of AIO Tests."
          );
          isAttachmentAPIAvailable = false;
        }
      } else {
        if (error.data) {
          AioTestsLogger.error(error.data);
        } else {
          AioTestsLogger.error(error.code);
        }
      }
    }
  }
}

async function initCycleDetails(aioConfig: AioConfig) {
  try {
    const data = await getOrCreateCycle(aioConfig);
    if (aioConfig.cycleDetails.cycleKeyToReportTo) {
      AioTestsLogger.log("Reporting results to cycle : " + aioConfig.cycleDetails.cycleKeyToReportTo);
    } else {
      stopReporting = true;
      if (typeof data === "string") {
        AioTestsLogger.error(data);
      }
    }
  } catch (err) {
    if (err instanceof Error) {
      AioTestsLogger.error("An error occurred: " + err.message);
    } else {
      AioTestsLogger.error("An unknown error occurred");
    }
    stopReporting = true;
  }
}

export const reportTestResults = async function (config: AioConfig, testSummary: TestData[], multiBrowsers: boolean): Promise<void> {
  if(testSummary && testSummary.length) {
    await initCycleDetails(config);
    if (!aioAPIClient || stopReporting) {
      return Promise.resolve();
    }
    multiBrowser = multiBrowsers;
    const cfUpdates = config.runDetails? getCustomFieldValueToUpdate(config.runDetails.customFieldsToUpdate) : null;

    let index = 0;
    const total = testSummary.length;
    AioTestsLogger.logStartEnd("Reporting results");
    for (const {result, test} of testSummary) {
      const isLast = index === total - 1;
      await reportSpecResults(config, result, test, cfUpdates);

      if (isLast && bulkTestRunUpdate) {
        await bulkUpdateResult(config);
      }
      index++;
    }
  }else {
    AioTestsLogger.log("No case keys found in specs");
  }
};

const reportSpecResults = async function (config: AioConfig, results: TestResult, testCase: TestCase, cfUpdates: CustomFieldUpdateData[]|null): Promise<void> {
  const testData = await findResults(results, testCase);
  const passedCaseKeys: string[] = [];
  const failedCaseKeys: string[] = [];

  const targetKeys =
    results.status === "passed" ? passedCaseKeys : failedCaseKeys;
  targetKeys.push(...testData.keys());
  allCaseKeys.push(...targetKeys);

  if (failedCaseKeys.length > 0) {
    AioTestsLogger.log("*".repeat(5) + " Updating failed cases " + "*".repeat(5));
    try {
      await postFailedResult(failedCaseKeys, testData, config, cfUpdates);
    } catch (err) {
      debugLogError(err);
    }
  }

  await addDataForBulkUpdateResult(passedCaseKeys, testData, config, cfUpdates);

};

async function postFailedResult(failedCaseKeys: string[], testData: Map<string, TestDataEntry>, config: AioConfig, cfUpdates: CustomFieldUpdateData[]|null): Promise<void> {
  return failedCaseKeys.reduce<Promise<void>>((promiseChain, caseKey) => {
    const attemptData = testData.get(caseKey);
    return promiseChain.then(async () => {
      await postResult(config, caseKey, attemptData!, attemptData!.screenshot!, cfUpdates);
    });
  }, Promise.resolve());
}

async function findResults(results: TestResult, testCase: TestCase): Promise<Map<string, TestDataEntry>> {
  let testData: Map<string, TestDataEntry> = new Map<string, TestDataEntry>();
  let pattern = new RegExp("\\w+-TC-\\d+", "gi");
  let tcKeys: string[] = [];
  let match;
  testCase.tags.forEach((tag) => {
    do {
      match = pattern.exec(tag);
      if (match) {
        tcKeys.push(match[0]);
      }
    } while (match != null);
  });
  if (tcKeys.length) {
    tcKeys.forEach((tcKey) => {
      // @ts-ignore
      const comments = results.error || multiBrowser ? generateComments(testCase._projectId ? testCase._projectId : "", results.error) : null;
      // @ts-ignore
      testData.set(tcKey, {
        testId: tcKey,
        duration: results.duration / 1000,
        testStatus: getAIORunStatus(results.status),
        retires: results.retry,
        comments: comments,
        screenshot: results.attachments
          .filter((attachment) => attachment.contentType.startsWith("image"))
          .map((attachment) => attachment.path)
          .filter((path) => path !== undefined),
        // @ts-ignore
        browser: testCase._projectId!
      });
    });
  }
  return testData;
}

function generateComments(browserName: string, error?: { message?: string; snippet?: string; stack?: string; value?: string; }): string[] {
  return [
    multiBrowser ? `Browser: ${browserName}` : undefined,
    error?.message ? `Message: ${error.message}` : undefined,
    error?.stack ? `Stack: ${error.stack}` : undefined,
  ].filter(Boolean) as string[];
}

async function addDataForBulkUpdateResult(passedCaseKeys: string[], testDataMap: Map<string, TestDataEntry>, config: AioConfig, cfUpdates: CustomFieldUpdateData[]|null): Promise<void> {

  for (const passedCaseKey of passedCaseKeys) {
    const testData = testDataMap.get(passedCaseKey);
    if (!testData) continue;

    const testRun = {
      testCaseKey: passedCaseKey,
      testRunStatus: testData.testStatus,
      effort: testData.duration,
      isAutomated: true,
      comments: testData.comments,
      customFieldValueToUpdate:
        config.runDetails
          ? replaceProjectNameForRunFields(cfUpdates, testData.browser)
          : cfUpdates,
    };

    if (multiBrowser) {
      const existingRequest = browserBulkRequestMap.get(testData.browser);
      if (existingRequest) {
        existingRequest.testRuns.push(testRun);
      } else {
        browserBulkRequestMap.set(testData.browser, { testRuns: [testRun] });
      }
    } else {
      bulkRequestBody.testRuns.push(testRun);
    }

    bulkTestRunUpdate = true;
  }
}

async function bulkUpdateResult(aioConfig: AioConfig, trialCounter = 0): Promise<void> {
  const { jiraProjectId, cycleDetails, addNewRun } = aioConfig;
  const url = `/project/${jiraProjectId}/testcycle/${cycleDetails.cycleKeyToReportTo}/bulk/testrun/update?createNewRun=${!!addNewRun}`;

  AioTestsLogger.log("*".repeat(5) + " Updating all passed cases " + "*".repeat(5));
  AioTestsLogger.debug(`Reporting results in bulk: ${url}`);

  try {
    const processResponse = (response: AxiosResponse, browser?: String) => {
      const { successCount, errorCount, errors } = response.data;
      AioTestsLogger.log(`Successfully reported ${successCount} passed cases${browser ? ` for browser: ${browser}` : ""}.`);
      if (errorCount > 0) {
        AioTestsLogger.error("Failures in reporting passed cases:");
        Object.entries(errors).forEach(([key, error]) => {
          const errorMessage =
            (error as { message?: string }).message || "Unknown error";
          AioTestsLogger.error(`${key}: ${errorMessage}`);
        });
      }
    };

    if (multiBrowser) {
      for (const [browser, bulkRequestBodyBrowser] of browserBulkRequestMap) {
        const response = await aioAPIClient!.post(url, bulkRequestBodyBrowser);
        processResponse(response, browser);
      }
    } else {
      const response = await aioAPIClient!.post(url, bulkRequestBody);
      processResponse(response);
    }
  } catch (err: any) {
    debugLogError(err);

    if (err.response?.status === 429 && trialCounter < 3) {
      AioTestsLogger.log("Reached AIO rate limits. Retrying...");
      await sleep(rateLimitWaitTime);
      return bulkUpdateResult(aioConfig, trialCounter + 1);
    }

    const errorMsg = err.response
      ? `Status Code - ${err.response.status}: ${JSON.stringify(
          err.response.data
        )}`
      : err.code;
    AioTestsLogger.error(`Error in bulk cases reporting: ${errorMsg}`);
    stopReporting = true;
  }
}

async function postResult(aioConfig: AioConfig, caseKey: string, results: TestDataEntry, screenshots: (string|undefined)[], cfUpdates:CustomFieldUpdateData[]|null, trialCounter: number = 0): Promise<void> {
  const data: Record<string, any> = {
    testRunStatus: results.testStatus,
    effort: results.duration,
    isAutomated: true,
    comments: results.comments,
    customFieldValueToUpdate:
        aioConfig.runDetails
            ? replaceProjectNameForRunFields(cfUpdates, results.browser)
            : cfUpdates,
  };
  const createNewRun = aioConfig.addNewRun;

  AioTestsLogger.debug(`Posting results to /project/${aioConfig.jiraProjectId}/testcycle/${aioConfig.cycleDetails.cycleKeyToReportTo}/testcase/${caseKey}/testrun?createNewRun=${createNewRun}`);

  try {
    const response = await aioAPIClient!.post(
      `/project/${aioConfig.jiraProjectId}/testcycle/${aioConfig.cycleDetails.cycleKeyToReportTo}/testcase/${caseKey}/testrun?createNewRun=${createNewRun}`,
      data);

    if (multiBrowser) {
      if (browserKeyMap.has(results.browser)) {
        browserKeyMap.get(results.browser)?.push(response.data.ID);
      } else {
        browserKeyMap.set(results.browser, [response.data.ID]);
      }
    } else {
      allFailedRuns.push(response.data.ID);
    }

    AioTestsLogger.log(`Successfully reported ${caseKey} as ${data.testRunStatus} with runID ${response.data.ID}.`);

    const runId = response.data.ID;
    if (aioConfig.addAttachmentToFailedCases && data.testRunStatus.toLowerCase() === "failed" && (isAttachmentAPIAvailable || isAttachmentAPIAvailable == null)) {
      return await uploadAttachments(aioConfig.jiraProjectId, aioConfig.cycleDetails.cycleKeyToReportTo!, runId, screenshots);
    }
  } catch (err) {
    debugLogError(err);
    if (isAxiosError(err) && err.response) {
      if (err.response.status === 429 && trialCounter < 3) {
        AioTestsLogger.log("Reached AIO rate limits.  Pausing...");
        await sleep(rateLimitWaitTime);
        return postResult(aioConfig, caseKey, results, screenshots, cfUpdates,trialCounter + 1);
      } else {
        AioTestsLogger.error(
          `Error reporting ${caseKey} : Status Code - ${err.response.status} - ${err.response.data}`
        );
      }
    } else {
      AioTestsLogger.error(
        `Error in bulk cases reporting : ${(err as Error).message}`
      );
    }
  }
}

async function uploadAttachments(jiraProjectId: string, cycleKey: string, runId: string, resultScreenshots: (string|undefined)[]): Promise<void> {
  if (resultScreenshots) {
    return resultScreenshots.reduce(async (promise, screenshot) => {
      await promise;
      return uploadScreenshot(screenshot, jiraProjectId, cycleKey, runId);
    }, Promise.resolve());
  } else {
    return Promise.resolve();
  }
}
