import axios from 'axios';
import { BaseApiService } from '@/services/api/base';
import { BaseApiResponse, GroupedApprovals } from '@/types/api/api-general';
import {
  BulkApproveExpensePayload,
  CreateExpensePayload,
  CreateExpenseRealizationPayload,
  Expense,
  UpdateExpensePayload,
} from '@/types/api/expense';
import { httpClient } from '@/services/http/client';
import { formatDate } from '@/lib/helper';

export class ExpenseApiService extends BaseApiService<
  Expense,
  FormData,
  FormData
> {
  constructor(basePath: string) {
    super(basePath);
  }

  async create(
    payload: FormData
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const createExpenseRequestRes = await httpClient<
        BaseApiResponse<Expense>
      >(this.basePath, {
        method: 'POST',
        body: payload,
      });

      return createExpenseRequestRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async createRealization(
    id: number,
    payload: FormData
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const createExpenseRealizationRes = await httpClient<
        BaseApiResponse<Expense>
      >(`${this.basePath}/${id}/realizations`, {
        method: 'POST',
        body: payload,
      });

      return createExpenseRealizationRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async update(
    id: number,
    payload: FormData,
    deletedDocumentIds?: number[]
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      for (const deletedDocumentId of deletedDocumentIds ?? []) {
        await this.deleteExpenseRequestDocument(id, deletedDocumentId);
      }

      const updateExpenseRequestRes = await httpClient<
        BaseApiResponse<Expense>
      >(`${this.basePath}/${id}`, {
        method: 'PATCH',
        body: payload,
      });

      return updateExpenseRequestRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async updateRealization(
    id: number,
    payload: FormData
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const updateExpenseRealizationRes = await httpClient<
        BaseApiResponse<Expense>
      >(`${this.basePath}/${id}/realizations`, {
        method: 'PATCH',
        body: payload,
      });

      return updateExpenseRealizationRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async uploadRequestDocuments(
    id: number,
    files: File[]
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const updateExpenseRequestDocumentsFormData = new FormData();

      // files (multiple "documents" keys)
      files.forEach((file) => {
        updateExpenseRequestDocumentsFormData.append('documents', file);
      });

      const updateExpenseRealizationRes = await httpClient<
        BaseApiResponse<Expense>
      >(`${this.basePath}/${id}`, {
        method: 'PATCH',
        body: updateExpenseRequestDocumentsFormData,
      });

      return updateExpenseRealizationRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async uploadRealizationDocuments(
    id: number,
    files: File[]
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const updateExpenseRealizationDocumentsFormData = new FormData();

      // files (multiple "documents" keys)
      files.forEach((file) => {
        updateExpenseRealizationDocumentsFormData.append('documents', file);
      });

      const updateExpenseRealizationRes = await httpClient<
        BaseApiResponse<Expense>
      >(`${this.basePath}/${id}/realizations`, {
        method: 'PATCH',
        body: updateExpenseRealizationDocumentsFormData,
      });

      return updateExpenseRealizationRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async approveHeadArea(
    id: number,
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const approveRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/head-area`,
        {
          method: 'POST',
          body: {
            action: 'APPROVED',
            approvable_ids: [id],
            notes: notes,
          },
        }
      );

      return approveRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkApproveHeadArea(
    ids: number[],
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/head-area`,
        {
          method: 'POST',
          body: {
            action: 'APPROVED',
            approvable_ids: ids,
            notes: notes,
          },
        }
      );

      return bulkApproveRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async approveUnitVicePresident(
    id: number,
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const approveRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/unit-vice-president`,
        {
          method: 'POST',
          body: {
            action: 'APPROVED',
            approvable_ids: [id],
            notes: notes,
          },
        }
      );

      return approveRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkApproveUnitVicePresident(
    ids: number[],
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/unit-vice-president`,
        {
          method: 'POST',
          body: {
            action: 'APPROVED',
            approvable_ids: ids,
            notes: notes,
          },
        }
      );

      return bulkApproveRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async approveFinance(
    id: number,
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const approveRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/finance`,
        {
          method: 'POST',
          body: {
            action: 'APPROVED',
            approvable_ids: [id],
            notes: notes,
          },
        }
      );

      return approveRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkApproveFinance(
    ids: number[],
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/finance`,
        {
          method: 'POST',
          body: {
            action: 'APPROVED',
            approvable_ids: ids,
            notes: notes,
          },
        }
      );

      return bulkApproveRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkApproveToStatus(
    payload: BulkApproveExpensePayload
  ): Promise<BaseApiResponse<Expense | Expense[]> | undefined> {
    try {
      return await httpClient<BaseApiResponse<Expense | Expense[]>>(
        `${this.basePath}/approvals/bulk`,
        {
          method: 'POST',
          body: payload,
        }
      );
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense | Expense[]>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkApprovals(
    ids: number[],
    status: BulkApproveExpensePayload['status'] | 'SELESAI',
    date?: string,
    notes?: string
  ): Promise<BaseApiResponse<Expense | Expense[]> | undefined> {
    if (status === 'SELESAI') {
      const responses = await Promise.all(ids.map((id) => this.complete(id)));
      const failedResponse = responses.find(
        (response) => response?.status !== 'success'
      );

      if (failedResponse) {
        return failedResponse;
      }

      const completedExpenses = responses.flatMap((response) =>
        response?.status === 'success' ? [response.data] : []
      );

      return {
        code: 200,
        status: 'success',
        message:
          completedExpenses.length === 1
            ? 'Submit expense approval successfully'
            : 'Submit expense approvals successfully',
        data: completedExpenses,
      };
    }

    return this.bulkApproveToStatus({
      approvable_ids: ids,
      status,
      date: date || undefined,
      notes: notes || undefined,
    });
  }

  async rejectHeadArea(
    id: number,
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const rejectRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/head-area`,
        {
          method: 'POST',
          body: {
            action: 'REJECTED',
            approvable_ids: [id],
            notes: notes,
          },
        }
      );

      return rejectRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkRejectHeadArea(
    ids: number[],
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/head-area`,
        {
          method: 'POST',
          body: {
            action: 'REJECTED',
            approvable_ids: ids,
            notes: notes,
          },
        }
      );

      return bulkRejectRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async rejectUnitVicePresident(
    id: number,
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const rejectRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/unit-vice-president`,
        {
          method: 'POST',
          body: {
            action: 'REJECTED',
            approvable_ids: [id],
            notes: notes,
          },
        }
      );

      return rejectRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkRejectUnitVicePresident(
    ids: number[],
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/unit-vice-president`,
        {
          method: 'POST',
          body: {
            action: 'REJECTED',
            approvable_ids: ids,
            notes: notes,
          },
        }
      );

      return bulkRejectRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async rejectFinance(
    id: number,
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const rejectRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/finance`,
        {
          method: 'POST',
          body: {
            action: 'REJECTED',
            approvable_ids: [id],
            notes: notes,
          },
        }
      );

      return rejectRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async bulkRejectFinance(
    ids: number[],
    notes?: string
  ): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/approvals/finance`,
        {
          method: 'POST',
          body: {
            action: 'REJECTED',
            approvable_ids: ids,
            notes: notes,
          },
        }
      );

      return bulkRejectRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async complete(id: number): Promise<BaseApiResponse<Expense> | undefined> {
    try {
      const completeRes = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/${id}/complete`,
        {
          method: 'POST',
        }
      );

      return completeRes;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async setExpensePaidOff(id: number) {
    try {
      const res = await httpClient<BaseApiResponse<Expense>>(
        `${this.basePath}/${id}/pay`,
        {
          method: 'PATCH',
        }
      );

      return res;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async deleteExpenseRequestDocument(
    expenseId: number,
    documentId: number
  ): Promise<BaseApiResponse | undefined> {
    try {
      const deleteExpenseRequestDocument = await httpClient<BaseApiResponse>(
        `${this.basePath}/${expenseId}/documents/${documentId}`,
        {
          method: 'DELETE',
        }
      );

      return deleteExpenseRequestDocument;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async deleteExpenseRealizationDocument(
    expenseId: number,
    documentId: number
  ): Promise<BaseApiResponse | undefined> {
    try {
      const deleteExpenseRealizationDocument =
        await httpClient<BaseApiResponse>(
          `${this.basePath}/${expenseId}/realization-documents/${documentId}`,
          {
            method: 'DELETE',
          }
        );

      return deleteExpenseRealizationDocument;
    } catch (error) {
      if (axios.isAxiosError<BaseApiResponse>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  async getApprovalHistory(
    expenseId: number,
    group: boolean = true,
    page: number = 1,
    limit: number = 10
  ) {
    try {
      const approvalHistoryRes = await httpClient<GroupedApprovals>(
        '/approvals',
        {
          query: {
            module_name: 'EXPENSES',
            module_id: expenseId,
            group_step_number: group ? 'true' : 'false',
            page,
            limit,
          },
        }
      );

      return approvalHistoryRes;
    } catch (error) {
      if (axios.isAxiosError<GroupedApprovals>(error)) {
        return error.response?.data;
      }

      return undefined;
    }
  }

  convertExpenseRequestPayloadToFormData = (payload: CreateExpensePayload) => {
    const formData = new FormData();

    formData.append('category', payload.category);
    formData.append('location_id', String(payload.location_id));
    formData.append('transaction_date', payload.transaction_date);
    formData.append('supplier_id', String(payload.supplier_id));

    // files (multiple "documents" keys)
    payload.documents.forEach((file) => {
      formData.append('documents', file);
    });

    formData.append(
      'expense_nonstocks',
      JSON.stringify(payload.expense_nonstocks)
    );

    return formData;
  };

  convertExpenseRequestUpdatePayloadToFormData = (
    payload: UpdateExpensePayload
  ) => {
    const formData = new FormData();

    formData.append('category', payload.category);
    formData.append('location_id', String(payload.location_id));
    formData.append('transaction_date', payload.transaction_date);
    formData.append('supplier_id', String(payload.supplier_id));

    // files (multiple "documents" keys)
    payload.documents.forEach((file) => {
      formData.append('documents', file);
    });

    formData.append(
      'expense_nonstocks',
      JSON.stringify(payload.expense_nonstocks)
    );

    return formData;
  };

  convertExpenseRealizationPayloadToFormData = (
    payload: CreateExpenseRealizationPayload
  ) => {
    const formData = new FormData();

    formData.append('realization_date', payload.realization_date);

    // files (multiple "documents" keys)
    payload.documents.forEach((file) => {
      formData.append('documents', file);
    });

    formData.append('realizations', JSON.stringify(payload.realizations));

    return formData;
  };

  async exportToExcel(initialQueryString: string) {
    const params = new URLSearchParams(initialQueryString);

    params.set('export', 'excel');
    params.set('type', 'all');
    params.set('page', '1');
    params.set('limit', '99999999999');

    const queryString = `?${params.toString()}`;

    const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
      method: 'GET',
      responseType: 'blob',
    });

    const url = window.URL.createObjectURL(new Blob([res]));
    const link = document.createElement('a');
    link.href = url;

    const fileName = `BOP-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
    link.setAttribute('download', fileName);

    document.body.appendChild(link);
    link.click();
    link.remove();
  }

  async exportInputProgressToExcel(startDate: string, endDate: string) {
    const params = new URLSearchParams();

    params.set('export', 'excel');
    params.set('type', 'progress');
    params.set('start_date', formatDate(startDate, 'YYYY-MM-DD'));
    params.set('end_date', formatDate(endDate, 'YYYY-MM-DD'));

    const queryString = `?${params.toString()}`;

    const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
      method: 'GET',
      responseType: 'blob',
    });

    const url = window.URL.createObjectURL(new Blob([res]));
    const link = document.createElement('a');
    link.href = url;

    const fileName = `input-progres-BOP-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`;
    link.setAttribute('download', fileName);

    document.body.appendChild(link);
    link.click();
    link.remove();
  }
}

export const ExpenseApi = new ExpenseApiService('/expenses');
