import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody, GetSprintResponse, GetJqlSearchIssueParams, GetJqlSearchIssueResponse } from "@business-logic/types/JiraApi";
import axios from "axios";
import axiosRetry from "axios-retry";
import FormData from 'form-data';
import path from "path";
import fs from 'fs';

const jiraUrl = process.env.JIRA_SERVER!;
const jiraUsername = process.env.JIRA_USERNAME!;
const jiraApiKey = process.env.JIRA_API_KEY!;
const boardId = process.env.JIRA_BOARD_ID!;
const encodedAuthKey = Buffer.from(`${jiraUsername}:${jiraApiKey}`).toString('base64');

export default class JiraApiUtil {
    readonly baseUrl: string;
    readonly issueUrl: string;
    readonly bulkIssueCreateUrl: string;
    readonly bulkIssueFetchUrl: string;
    readonly bulkTransitionIssuesUrl: string;
    readonly getCurrentSprintUrl: string;
    readonly getJqlSearchIssueUrl: string;

    readonly axiosClient: axios.AxiosInstance;

    constructor() {
        this.baseUrl = jiraUrl;
        this.issueUrl = `${this.baseUrl}/rest/api/3/issue`;
        this.bulkIssueCreateUrl = `${this.issueUrl}/bulk`;
        this.bulkIssueFetchUrl = `${this.issueUrl}/bulkfetch`;
        this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition`;
        this.getCurrentSprintUrl = `${this.baseUrl}/rest/agile/1.0/board/${boardId}/sprint?state=active`;
        this.getJqlSearchIssueUrl = `${this.baseUrl}/rest/api/3/search/jql`;
        this.axiosClient = axios.create();
        // interceptor to log error message from api
        this.axiosClient.interceptors.response.use(
            response => response,
            error => {
                console.error('Axios Error:', error?.response?.data || error.message);
                return Promise.reject(error);
            }
        );
        // Set up retries if requests are made too quickly
        axiosRetry(this.axiosClient, {
            retries: 4,
            retryDelay: (retryCount) => { return Math.pow(2, retryCount) * 1000; },     // Exponential backoff
            retryCondition: (error) => { return error.response?.status === 429 }        // If rate-limit error
        });
    }

    getIssue(issueKey: string) {
        const url = `${this.issueUrl}/${issueKey}`;
        return this.axiosClient.get<GetIssueResponse>(url, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                Accept: 'application/json'
            }
        });
    }

    async getCurrentSprint() {
        const res = await this.axiosClient.get<GetSprintResponse>(this.getCurrentSprintUrl, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                Accept: 'application/json'
            }
        });
        const currentSprint = res.data.values.filter(sprint => {
            return `${sprint.originBoardId}` === boardId;
        });
        if (currentSprint.length === 1) {
            return currentSprint[0];
        } else {
            console.error(`JiraApiUtil >> Multiple active sprints found for board ${boardId}`);
        }
    }

    getJqlSearchIssue(params: GetJqlSearchIssueParams) {
        return this.axiosClient.get<GetJqlSearchIssueResponse>(this.getJqlSearchIssueUrl, {
            params: params,
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                Accept: 'application/json'
            }
        })
    }

    postBulkFetchIssues(requestBody: PostBulkFetchIssuesRequestBody) {
        return this.axiosClient.post<PostBulkFetchIssuesResponse>(this.bulkIssueFetchUrl, requestBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        });
    }

    getIssueTransitions(issueKey: string) {
        const url = `${this.issueUrl}/${issueKey}/transitions`
        return this.axiosClient.get<GetIssueTransitionsResponse>(url, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                Accept: 'application/json'
            }
        });
    }

    getIssueTypes(projectKey: string) {
        const url =  `${this.baseUrl}/rest/api/3/issue/createmeta/${projectKey}/issuetypes`;
        return this.axiosClient.get<GetIssueTypesResponse>(url, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                Accept: 'application/json'
            }
        });
    }

    postAddComment(issueKey: string, commentBody: PostAddCommentResponse) {
        const url = `${this.issueUrl}/${issueKey}/comment`;
        return this.axiosClient.post(url, commentBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        });
    }

    postTransitionIssue(issueKey: string, requestBody: PostTransitionIssueRequestBody) {
        const transitionUrl = `${this.issueUrl}/${issueKey}/transitions`
        return this.axiosClient.post(transitionUrl, requestBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
        });
    }

    putEditIssue(issueKey: string, requestBody: PutEditIssueRequestBody) {
        const editIssueUrl = `${this.issueUrl}/${issueKey}`
        return this.axiosClient.put(editIssueUrl, requestBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        });
    }

    postBulkTransitionIssues(requestBody: PostBulkTransitionIssuesRequestBody) {
        return this.axiosClient.post(this.bulkTransitionIssuesUrl, requestBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
        });
    }
    
    postCreateIssue(requestBody: PostCreateIssueRequestBody) {
        return this.axiosClient.post<PostCreateIssueResponse>(this.issueUrl, requestBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
        });
    }

    postBulkCreateIssue(requestBody: PostBulkCreateIssueRequestBody) {
        return this.axiosClient.post<PostBulkCreateIssueResponse>(this.bulkIssueCreateUrl, requestBody, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
        });
    }

    postUploadAttachment(issueKey: string, filePath: string) {
        const url = `${this.baseUrl}/rest/api/3/issue/${issueKey}/attachments`
        const form = new FormData();
        const fileName = path.basename(filePath);

        form.append('file', fs.createReadStream(filePath), fileName);
        return this.axiosClient.post(url, form, {
            headers: {
                Authorization: `Basic ${encodedAuthKey}`,
                'X-Atlassian-Token': 'no-check',
                ...form.getHeaders()
            }
        });
    }
}