import type { SimpleAccountResponse } from './account'
import type { Attachment } from './attachment'
import { ApiClient } from './base'
import type { Currency } from './data'
import { type Page, type PageRequest } from './pagination'

export interface SimpleVendorResponse {
  id: number
  name: string
}

export interface SimpleCategoryResponse {
  id: number
  name: string
  color: string
}

export interface TransactionLineItemResponse {
  idx: number
  valuePerItem: number
  quantity: number
  description: string
  category: TransactionCategory | null
}

export interface TransactionVendor {
  id: number
  name: string
  description: string | null
}

export interface TransactionVendorPayload {
  name: string
  description: string | null
}

export interface TransactionCategory {
  id: number
  parentId: number | null
  name: string
  description: string | null
  color: string
}

export interface TransactionCategoryTree {
  id: number
  parentId: number | null
  name: string
  description: string
  color: string
  children: TransactionCategoryTree[]
  depth: number
}

export interface TransactionCategoryBalance {
  credits: number
  debits: number
  balance: number
  currency: Currency
}

export interface TransactionsListItem {
  id: number
  timestamp: string
  addedAt: string
  amount: number
  currency: Currency
  description: string
  internalTransfer: boolean
  vendor: SimpleVendorResponse | null
  category: SimpleCategoryResponse | null
  creditedAccount: SimpleAccountResponse | null
  debitedAccount: SimpleAccountResponse | null
  tags: string[]
}

export interface TransactionsListItemVendor {
  id: number
  name: string
}

export interface TransactionsListItemCategory {
  id: number
  name: string
  color: string
}

export interface TransactionsListItemAccount {
  id: number
  name: string
  type: string
  numberSuffix: string
}

export interface TransactionDetail {
  id: number
  timestamp: string
  addedAt: string
  amount: number
  currency: Currency
  description: string
  internalTransfer: boolean
  vendor: TransactionVendor | null
  category: TransactionCategory | null
  creditedAccount: SimpleAccountResponse | null
  debitedAccount: SimpleAccountResponse | null
  tags: string[]
  lineItems: TransactionLineItemResponse[]
  attachments: Attachment[]
}

export interface AddTransactionPayload {
  timestamp: string
  amount: number
  currencyCode: string
  description: string
  internalTransfer: boolean
  vendorId: number | null
  categoryId: number | null
  creditedAccountId: number | null
  debitedAccountId: number | null
  tags: string[]
  lineItems: AddTransactionPayloadLineItem[]
  attachmentIdsToRemove: number[]
}

export interface AddTransactionPayloadLineItem {
  valuePerItem: number
  quantity: number
  description: string
  categoryId: number | null
}

export interface CreateCategoryPayload {
  name: string
  description: string | null
  color: string
  parentId: number | null
}

export interface AggregateTransactionCurrencyData {
  credits: number
  debits: number
  balance: number
  currency: Currency
}

export interface AggregateTransactionData {
  currencies: AggregateTransactionCurrencyData[]
}

export interface TransactionDraftListItem {
  id: number
  addedAt: string
  templateName: string | null
  timestamp: string | null
  amount: number | null
  currency: Currency | null
  description: string | null
  internalTransfer: boolean | null
  vendor: SimpleVendorResponse | null
  category: SimpleCategoryResponse | null
  creditedAccount: SimpleAccountResponse | null
  debitedAccount: SimpleAccountResponse | null
  tags: string[]
}

export interface TransactionDraftResponse {
  id: number
  addedAt: string
  templateName: string | null
  timestamp: string | null
  amount: number | null
  currency: Currency | null
  description: string | null
  internalTransfer: boolean | null
  vendor: SimpleVendorResponse | null
  category: SimpleCategoryResponse | null
  creditedAccount: SimpleAccountResponse | null
  debitedAccount: SimpleAccountResponse | null
  tags: string[]
  lineItems: TransactionLineItemResponse[]
  attachments: Attachment[]
}

export interface TransactionDraftPayload {
  templateName: string | null
  timestamp: string | null
  amount: number | null
  currencyCode: string | null
  description: string | null
  internalTransfer: boolean | null
  vendorId: number | null
  categoryId: number | null
  creditedAccountId: number | null
  debitedAccountId: number | null
  tags: string[]
  lineItems: AddTransactionPayloadLineItem[]
  attachmentIdsToRemove: number[]
}

export interface RecurringTransactionPayload {
  draftId: number
  scheduleExpr: string
}

export interface RecurringTransactionResponse {
  id: number
  draftId: number
  scheduleExpr: string
}

export class TransactionApiClient extends ApiClient {
  readonly path: string

  constructor(profileName: string) {
    super()
    this.path = `/profiles/${profileName}`
  }

  getVendors(): Promise<TransactionVendor[]> {
    return super.getJson(this.path + '/vendors')
  }

  getVendor(id: number): Promise<TransactionVendor> {
    return super.getJson(this.path + '/vendors/' + id)
  }

  createVendor(data: TransactionVendorPayload): Promise<TransactionVendor> {
    return super.postJson(this.path + '/vendors', data)
  }

  updateVendor(id: number, data: TransactionVendorPayload): Promise<TransactionVendor> {
    return super.putJson(this.path + '/vendors/' + id, data)
  }

  deleteVendor(id: number): Promise<void> {
    return super.delete(this.path + '/vendors/' + id)
  }

  getCategories(): Promise<TransactionCategoryTree[]> {
    return super.getJson(this.path + '/categories')
  }

  async getCategoriesFlattened(): Promise<TransactionCategoryTree[]> {
    const categories = await this.getCategories()
    const flat: TransactionCategoryTree[] = []
    await this.flattenCategories(flat, categories)
    return flat
  }

  private flattenCategories(arr: TransactionCategoryTree[], tree: TransactionCategoryTree[]) {
    for (const category of tree) {
      arr.push(category)
      this.flattenCategories(arr, category.children)
    }
  }

  getCategory(id: number): Promise<TransactionCategory> {
    return super.getJson(this.path + '/categories/' + id)
  }

  getChildCategories(parentId: number): Promise<TransactionCategory[]> {
    return super.getJson(this.path + '/categories/' + parentId + '/children')
  }

  getCategoryBalances(id: number, includeChildren: boolean): Promise<TransactionCategoryBalance[]> {
    return super.getJson(
      this.path + '/categories/' + id + '/balances?includeChildren=' + includeChildren,
    )
  }

  createCategory(data: CreateCategoryPayload): Promise<TransactionCategory> {
    return super.postJson(this.path + '/categories', data)
  }

  updateCategory(id: number, data: CreateCategoryPayload): Promise<TransactionCategory> {
    return super.putJson(this.path + '/categories/' + id, data)
  }

  deleteCategory(id: number): Promise<void> {
    return super.delete(this.path + '/categories/' + id)
  }

  getTransactions(
    paginationOptions: PageRequest | undefined = undefined,
  ): Promise<Page<TransactionsListItem>> {
    return super.getJsonPage(this.path + '/transactions', paginationOptions)
  }

  searchTransactions(
    params: URLSearchParams,
    paginationOptions: PageRequest | undefined = undefined,
  ): Promise<Page<TransactionsListItem>> {
    if (paginationOptions !== undefined) {
      params.append('page', paginationOptions.page + '')
      params.append('size', paginationOptions.size + '')
      for (const sort of paginationOptions.sorts) {
        params.append('sort', sort.attribute + ',' + sort.dir)
      }
    }
    return super.getJson(this.path + '/transactions/search?' + params.toString())
  }

  getAggregateTransactionData(params: URLSearchParams): Promise<AggregateTransactionData> {
    return super.getJson(this.path + '/transactions/aggregate-data?' + params.toString())
  }

  exportTransactions(params: URLSearchParams): Promise<void> {
    return super.getFile(this.path + '/transactions/export?' + params.toString())
  }

  getTransaction(id: number): Promise<TransactionDetail> {
    return super.getJson(this.path + '/transactions/' + id)
  }

  addTransaction(data: AddTransactionPayload, files: File[] = []): Promise<TransactionDetail> {
    const formData = new FormData()
    formData.append('payload', JSON.stringify(data))
    for (const file of files) {
      formData.append('file', file)
    }
    return super.postFormData(this.path + '/transactions', formData)
  }

  updateTransaction(
    id: number,
    data: AddTransactionPayload,
    files: File[] = [],
  ): Promise<TransactionDetail> {
    const formData = new FormData()
    formData.append('payload', JSON.stringify(data))
    for (const file of files) {
      formData.append('file', file)
    }
    return super.putFormData(this.path + '/transactions/' + id, formData)
  }

  deleteTransaction(id: number): Promise<void> {
    return super.delete(this.path + '/transactions/' + id)
  }

  getAllTags(): Promise<string[]> {
    return super.getJson(this.path + '/transaction-tags')
  }

  // Drafts:

  getDrafts(
    paginationOptions: PageRequest | undefined = undefined,
  ): Promise<Page<TransactionDraftListItem>> {
    return super.getJsonPage(this.path + '/transaction-drafts', paginationOptions)
  }

  getTemplateDrafts(
    paginationOptions: PageRequest | undefined = undefined,
  ): Promise<Page<TransactionDraftListItem>> {
    const params = new URLSearchParams()
    params.append('template', 'true')
    if (paginationOptions !== undefined) {
      params.append('page', paginationOptions.page + '')
      params.append('size', paginationOptions.size + '')
      for (const sort of paginationOptions.sorts) {
        params.append('sort', sort.attribute + ',' + sort.dir)
      }
    }
    return super.getJson(this.path + '/transaction-drafts?' + params.toString())
  }

  getDraft(id: number): Promise<TransactionDraftResponse> {
    return super.getJson(this.path + '/transaction-drafts/' + id)
  }

  addDraft(data: TransactionDraftPayload, files: File[] = []): Promise<TransactionDraftResponse> {
    const formData = new FormData()
    formData.append('payload', JSON.stringify(data))
    for (const file of files) {
      formData.append('file', file)
    }
    return super.postFormData(this.path + '/transaction-drafts', formData)
  }

  updateDraft(
    id: number,
    data: TransactionDraftPayload,
    files: File[] = [],
  ): Promise<TransactionDraftResponse> {
    const formData = new FormData()
    formData.append('payload', JSON.stringify(data))
    for (const file of files) {
      formData.append('file', file)
    }
    return super.putFormData(this.path + '/transaction-drafts/' + id, formData)
  }

  deleteDraft(id: number): Promise<void> {
    return super.delete(this.path + '/transaction-drafts/' + id)
  }

  getRecurringTransactionsForDraft(draftId: number): Promise<RecurringTransactionResponse[]> {
    return super.getJson(`${this.path}/transaction-drafts/${draftId}/recurring-transactions`)
  }

  getRecurringTransactions(
    paginationOptions: PageRequest | undefined = undefined,
  ): Promise<Page<RecurringTransactionResponse>> {
    return super.getJsonPage(this.path + '/recurring-transactions', paginationOptions)
  }

  createRecurringTransaction(
    data: RecurringTransactionPayload,
  ): Promise<RecurringTransactionResponse> {
    return super.postJson(this.path + '/recurring-transactions', data)
  }

  deleteRecurringTransaction(id: number): Promise<void> {
    return super.delete(`${this.path}/recurring-transactions/${id}`)
  }
}
