import { objectKeys } from 'ts-extras';
import { Account, API, SaveTransaction } from 'ynab';
import { z } from 'zod';

type YNABConfig = z.infer<typeof YNABEnvVarsSchema>;

export class YNABClient {
  #config: YNABConfig;
  #ynabApi: API;

  constructor(config: YNABConfig) {
    this.#config = config;
    this.#ynabApi = new API(this.#config.YNAB_API_TOKEN);
  }

  async getDefaultBudget() {
    const budgetsResponse = await this.#ynabApi.budgets.getBudgets();
    const firstBudget = budgetsResponse.data.budgets[0];
    if (!firstBudget) throw new Error('No budget found.');
    return firstBudget;
  }

  async getAccounts(): Promise<Account[]> {
    const response = await this.#ynabApi.accounts.getAccounts(this.#config.DEFAULT_BUDGET_ID);
    return response.data.accounts;
  }

  async getCategoryGroups() {
    const budget = await this.getDefaultBudget();
    const categoriesResponse = await this.#ynabApi.categories.getCategories(budget.id);
    return categoriesResponse.data.category_groups;
  }

  async getCategoriesFlat() {
    const categoryGroups = await this.getCategoryGroups();
    const categoriesFlat = categoryGroups
      .filter((group) => !group.hidden)
      .map((group) => group.categories)
      .flat();
    return categoriesFlat;
  }

  async addTransactions(transactions: TransactionDetails[]) {
    try {
      await this.#ynabApi.transactions.createTransaction(this.#config.DEFAULT_BUDGET_ID, {
        transactions: transactions.map((transaction) => this.createTransaction(transaction)),
      });
    } catch (e) {
      console.error(e);
    }
  }

  createTransaction(transactionDetails: TransactionDetails): SaveTransaction {
    return {
      account_id: transactionDetails.accountId,
      amount: transactionDetails.cents * 10,
      date: new Date().toISOString().split('T')[0],
      payee_name: transactionDetails.payeeName,
      memo: transactionDetails.memo,
      category_id: transactionDetails.categoryId,
    };
  }
}
export const YNABEnvVarsSchema = z.object({
  YNAB_API_TOKEN: z.string(),
  DEFAULT_BUDGET_ID: z.string(),
});

export const YNAB_ENV_VARS = objectKeys(YNABEnvVarsSchema.keyof().Values);

export const TransactionDetailsSchema = z.object({
  accountId: z.string(),
  cents: z.number(),
  payeeName: z.string(),
  memo: z.string().optional(),
  categoryId: z.string(),
});

export type TransactionDetails = z.infer<typeof TransactionDetailsSchema>;
