import 'dart:async';

import 'package:isar/isar.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:path_provider/path_provider.dart';

import 'collections/budget.dart';

Future<Isar> openDatabase() async {
  final dir = await getApplicationDocumentsDirectory();
  return Isar.open([
    AccountSchema,
    BeneficiarySchema,
    TransactionSchema,
    TransactionTemplateSchema,
    RecurringTransactionSchema,
    ExpenseCategorySchema,
    BudgetSchema,
    BudgetItemSchema,
  ], directory: dir.path);
}

Future<List<Account>> getAccounts() {
  return GetIt.I.get<Isar>().accounts.where().findAll();
}

Future<double> getTotalBalance(Account account) async {
  return GetIt.I
      .get<Isar>()
      .transactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .amountProperty()
      .sum();
}

Future<List<Transaction>> getLastTransactions(
  Account account,
  DateTime today,
  int days,
) async {
  return GetIt.I
      .get<Isar>()
      .transactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .dateGreaterThan(toMidnight(today.subtract(Duration(days: days))))
      .findAll();
}

Future<List<RecurringTransaction>> getRecurringTransactions(Account? account) {
  if (account == null) {
    return Future.value([]);
  }

  return GetIt.I
      .get<Isar>()
      .recurringTransactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .findAll();
}

Stream<void> watchRecurringTransactions(Account account) {
  final account = GetIt.I.get<CoreCubit>().activeAccount!;
  return GetIt.I
      .get<Isar>()
      .recurringTransactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .build()
      .watchLazy(fireImmediately: true);
}

Future<void> upsertAccount(Account account) async {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    print("Before account insert");
    final id = await db.accounts.put(account);
    print("After account insert: $id");
  });
}

Future<void> upsertBeneficiary(Beneficiary beneficiary) async {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    await db.beneficiarys.put(beneficiary);
    await beneficiary.account.save();
  });
}

Future<Beneficiary?> getAccountBeneficiary(Account account) {
  return GetIt.I
      .get<Isar>()
      .beneficiarys
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .findFirst();
}

Future<void> upsertTransactionTemplate(TransactionTemplate template) async {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    await db.transactionTemplates.put(template);
    await template.beneficiary.save();
    await template.account.save();
    await template.expenseCategory.save();
  });
}

Future<void> upsertRecurringTransaction(RecurringTransaction template) async {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    await db.recurringTransactions.put(template);
    await template.template.save();
    await template.account.save();
  });
}

Future<void> upsertTransaction(Transaction transaction) async {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    await db.transactions.put(transaction);
    await transaction.beneficiary.save();
    await transaction.account.save();
    await transaction.expenseCategory.save();
  });
}

Stream<void> watchAccounts() {
  return GetIt.I.get<Isar>().accounts.watchLazy();
}

Stream<void> watchTransactionTemplates(Account account) {
  return GetIt.I
      .get<Isar>()
      .transactionTemplates
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .recurringEqualTo(false)
      .watchLazy(fireImmediately: true);
}

Future<List<TransactionTemplate>> getTransactionTemplates(Account? account) {
  if (account == null) {
    return Future.value([]);
  }

  return GetIt.I
      .get<Isar>()
      .transactionTemplates
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .recurringEqualTo(false)
      .findAll();
}

Stream<void> watchTransactions(Account account) {
  return GetIt.I
      .get<Isar>()
      .transactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .watchLazy(fireImmediately: true);
}

Future<List<Transaction>> getTransactions(Account? account) {
  if (account == null) {
    return Future.value([]);
  }

  return GetIt.I
      .get<Isar>()
      .transactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .findAll();
}

Stream<void> watchBeneficiaries() {
  return GetIt.I.get<Isar>().beneficiarys.watchLazy(fireImmediately: true);
}

Future<List<Beneficiary>> getBeneficiaries() {
  return GetIt.I.get<Isar>().beneficiarys.where().findAll();
}

Stream<Beneficiary?> watchBeneficiaryObject(Id id) {
  return GetIt.I.get<Isar>().beneficiarys.watchObject(id);
}

Future<void> upsertExpenseCategory(ExpenseCategory category) {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() => db.expenseCategorys.put(category));
}

Future<List<ExpenseCategory>> getExpenseCategories() {
  return GetIt.I.get<Isar>().expenseCategorys.where().findAll();
}

Stream<void> watchExpenseCategory() {
  return GetIt.I.get<Isar>().expenseCategorys.watchLazy(fireImmediately: true);
}

Stream<void> watchBudgets(Account account) {
  return GetIt.I.get<Isar>().budgets.filter().account((q) => q.idEqualTo(account.id)).watchLazy(fireImmediately: true);
}

Future<List<Budget>> getBudgets(Account? account) {
  if (account == null) {
    return Future.value([]);
  }

  return GetIt.I.get<Isar>().budgets.filter().account((q) => q.idEqualTo(account.id)).findAll();
}

Future<void> upsertBudget(Budget budget) {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    await db.budgets.put(budget);
    await budget.items.save();
    await budget.account.save();
  });
}

Future<void> upsertBudgetItem(BudgetItem item) {
  final db = GetIt.I.get<Isar>();
  return db.writeTxn(() async {
    await db.budgetItems.put(item);
    await item.expenseCategory.save();
  });
}

enum TransactionQueryDateOption {
  thisMonth,
}

Future<List<Transaction>> getTransactionsInTimeframe(Account account, DateTime today, TransactionQueryDateOption option) async {
  final lower = switch (option) {
    TransactionQueryDateOption.thisMonth => DateTime(
      today.year,
      today.month,
      0,
    ),
  };
  final upper = switch (option) {
    TransactionQueryDateOption.thisMonth => monthEnding(today),
  };

  return GetIt.I.get<Isar>()
      .transactions
      .filter()
      .account((q) => q.idEqualTo(account.id))
      .dateBetween(lower, upper)
      .findAll();
}