import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:formz/formz.dart';
import 'package:krezus/domains/transaction/models/transaction_account.dart';
import 'package:krezus/domains/transaction/models/transaction_category.dart';
import 'package:krezus/domains/transaction/models/transaction_date.dart';
import 'package:krezus/domains/transaction/models/transaction_description.dart';
import 'package:krezus/domains/transaction/models/transaction_line.dart';
import 'package:krezus/domains/transaction/models/transaction_value.dart';
import 'package:krezus/repositories/metadata/models/category.dart';
import 'package:krezus/repositories/transactions/models/transaction.dart';
import 'package:krezus/repositories/transactions/transactions_repository.dart';
import 'package:uuid/uuid.dart';

part 'transaction_event.dart';
part 'transaction_state.dart';

class TransactionBloc extends Bloc<TransactionEvent, TransactionState> {
  final TransactionsRepository _transactionsRepository;

  TransactionBloc({required TransactionsRepository transactionsRepository})
      : _transactionsRepository = transactionsRepository,
        super(const TransactionState())  {
    on<TransactionsLoad>(_onAccountLoad);
    on<TransactionDateChange>(_onTransactionDateChange);
    on<TransactionCategoryChange>(_onTransactionCategoryChange);
    on<TransactionDescriptionChange>(_onTransactionDescriptionChange);
    on<TransactionAccountChange>(_onTransactionAccountChange);
    on<TransactionValueChange>(_onTransactionValueChange);
    on<TransactionOpenAddDialog>(_onTransactionOpenAddDialog);
    on<TransactionHideAddDialog>(_onTransactionHideAddDialog);
    on<TransactionAdd>(_onTransactionAddDialog);
    on<TransactionSetCurrent>(_onTransactionSetCurrent);
    on<TransactionDeleteCurrent>(_onTransactionDeleteCurrent);
    on<TransactionFilterCategory>(_onTransactionFilterCategory);

    _transactionsRepository
      .getTransactionsStream()
      .listen((transactions) => add(TransactionsLoad(transactions)));
  }

  _onAccountLoad(TransactionsLoad event, Emitter<TransactionState> emit) {
    var computeResult = _computeTransactionLine(event.transactions);
    emit(state.copyWith(
      transactions: event.transactions,
      transactionsLines: computeResult.list,
      transactionsLinesFiltered: _applyCategoryFilter(computeResult.list),
      globalTotal: computeResult.globalTotal,
      accountsTotals: computeResult.accountsTotals,
    ));
  }

  ({List<TransactionLine> list, double globalTotal, Map<String, double> accountsTotals, List<String> categories}) _computeTransactionLine(List<Transaction> transactions) {
    double globalTotal = 0;
    Map<String, double> accountsTotals = <String, double>{};
    List<TransactionLine> output = [];
    Set<String> categories = {};

    for(var transaction in transactions) {
      double subTotal = globalTotal + transaction.value;
      globalTotal = subTotal;

      double accountTotal = accountsTotals[transaction.account] ?? 0;
      accountTotal += transaction.value;
      accountsTotals[transaction.account] = accountTotal;
      categories.add(transaction.category);

      output.add(TransactionLine(transaction: transaction, subTotal: subTotal));
    }

    output.sort((a, b) => b.transaction.date.compareTo(a.transaction.date));

    return (list: output, globalTotal: globalTotal, accountsTotals: accountsTotals, categories: categories.toList());
  }

  _onTransactionDateChange(
    TransactionDateChange event, Emitter<TransactionState> emit
  ) {
    final transactionDate = TransactionDate.dirty(event.date);
    emit(state.copyWith(
      transactionDate: transactionDate,
      isValid: Formz.validate([transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]),
    ));
  }

  _onTransactionCategoryChange(
    TransactionCategoryChange event, Emitter<TransactionState> emit
  ) {
    final transactionCategory = TransactionCategory.dirty(event.category);
    emit(state.copyWith(
      transactionCategory: transactionCategory,
      isValid: Formz.validate([state.transactionDate, transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]),
    ));
  }

  _onTransactionDescriptionChange(
    TransactionDescriptionChange event, Emitter<TransactionState> emit
  ) {
    final transactionDescription = TransactionDescription.dirty(event.description);
    emit(state.copyWith(
      transactionDescription: transactionDescription,
      isValid: Formz.validate([state.transactionDate, state.transactionCategory, transactionDescription, state.transactionAccount, state.transactionValue]),
    ));
  }

  _onTransactionAccountChange(
    TransactionAccountChange event, Emitter<TransactionState> emit
  ) {
    final transactionAccount = TransactionAccount.dirty(event.account);
    emit(state.copyWith(
      transactionAccount: transactionAccount,
      isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, transactionAccount, state.transactionValue]),
    ));
  }

  _onTransactionValueChange(
    TransactionValueChange event, Emitter<TransactionState> emit
  ) {
    try {
      final transactionValue = TransactionValue.dirty(double.parse(event.value));
      emit(state.copyWith(
        transactionValue: transactionValue,
        isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]),
      ));
    } catch (e) {
      const transactionValue = TransactionValue.dirty(double.infinity);
      emit(state.copyWith(
        transactionValue: transactionValue,
        isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]),
      ));
    }
  }

  _onTransactionOpenAddDialog(
    TransactionOpenAddDialog event, Emitter<TransactionState> emit
  ) {
    emit(state.copyWith(showAddDialog: true));
  }

  _onTransactionHideAddDialog(
    TransactionHideAddDialog event, Emitter<TransactionState> emit
  ) {
    emit(state.copyWith(showAddDialog: false));
  }

  _onTransactionAddDialog(
    TransactionAdd event, Emitter<TransactionState> emit
  ) {
    if (state.isValid) {
      List<Transaction> transactions = state.transactions;
      Transaction? currentTransaction = state.currentTransaction;
      if (currentTransaction != null) {
        transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid);
      }

      transactions.add(Transaction(
        uuid: const Uuid().v8(),
        date: state.transactionDate.value ?? DateTime.now(),
        category: state.transactionCategory.value,
        description: state.transactionDescription.value,
        account: state.transactionAccount.value,
        value: state.transactionValue.value
      ));
      final computeResult = _computeTransactionLine(transactions);

      _transactionsRepository.saveTransactions(transactions);
      
      emit(state.copyWith(
        currentTransaction: null,
        transactionDate: const TransactionDate.pure(),
        transactionCategory: const TransactionCategory.pure(),
        transactionDescription: const TransactionDescription.pure(),
        transactionAccount: const TransactionAccount.pure(),
        transactionValue: const TransactionValue.pure(),
        transactions: transactions,
        transactionsLines: computeResult.list,
        transactionsLinesFiltered: _applyCategoryFilter(computeResult.list),
        globalTotal: computeResult.globalTotal,
        accountsTotals: computeResult.accountsTotals,
      ));
    }
  }

  _onTransactionSetCurrent(
    TransactionSetCurrent event, Emitter<TransactionState> emit
  ) {
    Transaction? transaction = event.transaction;
    if (transaction == null) {
      emit(state.copyWith(
        currentTransaction: event.transaction,
        transactionDate: const TransactionDate.pure(),
        transactionCategory: const TransactionCategory.pure(),
        transactionDescription: const TransactionDescription.pure(),
        transactionAccount: const TransactionAccount.pure(),
        transactionValue: const TransactionValue.pure(),
      ));
    } else {
      emit(state.copyWith(
        currentTransaction: transaction,
        transactionDate: TransactionDate.dirty(transaction.date),
        transactionCategory: TransactionCategory.dirty(transaction.category),
        transactionDescription: TransactionDescription.dirty(transaction.description),
        transactionAccount: TransactionAccount.dirty(transaction.account),
        transactionValue: TransactionValue.dirty(transaction.value),
      ));
    }
  }

  _onTransactionDeleteCurrent(
    TransactionDeleteCurrent event, Emitter<TransactionState> emit
  ) {
    Transaction? currentTransaction = state.currentTransaction;
    if (currentTransaction != null) {
      List<Transaction> transactions = state.transactions;
      transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid);
      final computeResult = _computeTransactionLine(transactions);
      _transactionsRepository.saveTransactions(transactions);
      emit(state.copyWith(
        currentTransaction: null,
        transactionDate: const TransactionDate.pure(),
        transactionCategory: const TransactionCategory.pure(),
        transactionDescription: const TransactionDescription.pure(),
        transactionAccount: const TransactionAccount.pure(),
        transactionValue: const TransactionValue.pure(),
        transactions: transactions,
        transactionsLines: computeResult.list,
        transactionsLinesFiltered: _applyCategoryFilter(computeResult.list),
        globalTotal: computeResult.globalTotal,
        accountsTotals: computeResult.accountsTotals,
      ));
    }
  }
  

  _onTransactionFilterCategory(TransactionFilterCategory event, Emitter<TransactionState> emit) {
    List<TransactionLine> transactionsLinesFiltered = state.transactionsLines;
    String? categoryLabel = event.category?.label;
    if (categoryLabel != null) {
      transactionsLinesFiltered = state.transactionsLines.where((transaction) => transaction.transaction.category == categoryLabel).toList();
    }

    emit(state.copyWith(
      transactionsLinesFiltered: transactionsLinesFiltered,
      categoryFilter: event.category,
    ));
  }

  List<TransactionLine> _applyCategoryFilter(List<TransactionLine> transactionsLines) {
    List<TransactionLine> transactionsLinesFiltered = transactionsLines;
    String? categoryLabel = state.categoryFilter?.label;
    if (categoryLabel != null) {
      transactionsLinesFiltered = state.transactionsLines.where((transaction) => transaction.transaction.category == categoryLabel).toList();
    }
    return transactionsLinesFiltered;
  }
}