import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/usecases/get_current_bill_amount.dart';
import '../../domain/usecases/get_invoice_history.dart';
import '../../domain/usecases/get_pending_invoices.dart';
import '../../domain/usecases/get_savings_amount.dart';
import '../../domain/usecases/get_spending_breakdown.dart';
import '../models/billing_invoice_model.dart';
import '../models/spending_breakdown_model.dart';
import 'billing_event.dart';
import 'billing_state.dart';

/// BLoC for managing billing state and data loading.
class BillingBloc extends Bloc<BillingEvent, BillingState>
    with BlocErrorHandler<BillingState> {
  /// Creates a [BillingBloc] with the given use cases.
  BillingBloc({
    required GetCurrentBillAmountUseCase getCurrentBillAmount,
    required GetSavingsAmountUseCase getSavingsAmount,
    required GetPendingInvoicesUseCase getPendingInvoices,
    required GetInvoiceHistoryUseCase getInvoiceHistory,
    required GetSpendingBreakdownUseCase getSpendingBreakdown,
  }) : _getCurrentBillAmount = getCurrentBillAmount,
       _getSavingsAmount = getSavingsAmount,
       _getPendingInvoices = getPendingInvoices,
       _getInvoiceHistory = getInvoiceHistory,
       _getSpendingBreakdown = getSpendingBreakdown,
       super(const BillingState()) {
    on<BillingLoadStarted>(_onLoadStarted);
    on<BillingPeriodChanged>(_onPeriodChanged);
  }

  final GetCurrentBillAmountUseCase _getCurrentBillAmount;
  final GetSavingsAmountUseCase _getSavingsAmount;
  final GetPendingInvoicesUseCase _getPendingInvoices;
  final GetInvoiceHistoryUseCase _getInvoiceHistory;
  final GetSpendingBreakdownUseCase _getSpendingBreakdown;

  Future<void> _onLoadStarted(
    BillingLoadStarted event,
    Emitter<BillingState> emit,
  ) async {
    emit(state.copyWith(status: BillingStatus.loading));
    await handleError(
      emit: emit,
      action: () async {
        final List<dynamic> results =
            await Future.wait<dynamic>(<Future<dynamic>>[
          _getCurrentBillAmount.call(),
          _getSavingsAmount.call(),
          _getPendingInvoices.call(),
          _getInvoiceHistory.call(),
          _getSpendingBreakdown.call(state.period),
        ]);

        final double savings = results[1] as double;
        final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
        final List<Invoice> invoiceHistory = results[3] as List<Invoice>;
        final List<InvoiceItem> spendingItems = results[4] as List<InvoiceItem>;

        // Map Domain Entities to Presentation Models
        final List<BillingInvoice> uiPendingInvoices =
            pendingInvoices.map(_mapInvoiceToUiModel).toList();
        final List<BillingInvoice> uiInvoiceHistory =
            invoiceHistory.map(_mapInvoiceToUiModel).toList();
        final List<SpendingBreakdownItem> uiSpendingBreakdown =
            _mapSpendingItemsToUiModel(spendingItems);
        final double periodTotal = uiSpendingBreakdown.fold(
          0.0,
          (double sum, SpendingBreakdownItem item) => sum + item.amount,
        );

        emit(
          state.copyWith(
            status: BillingStatus.success,
            currentBill: periodTotal,
            savings: savings,
            pendingInvoices: uiPendingInvoices,
            invoiceHistory: uiInvoiceHistory,
            spendingBreakdown: uiSpendingBreakdown,
          ),
        );
      },
      onError: (String errorKey) => state.copyWith(
        status: BillingStatus.failure,
        errorMessage: errorKey,
      ),
    );
  }

  Future<void> _onPeriodChanged(
    BillingPeriodChanged event,
    Emitter<BillingState> emit,
  ) async {
    await handleError(
      emit: emit,
      action: () async {
        final List<InvoiceItem> spendingItems =
            await _getSpendingBreakdown.call(event.period);
        final List<SpendingBreakdownItem> uiSpendingBreakdown =
            _mapSpendingItemsToUiModel(spendingItems);
        final double periodTotal = uiSpendingBreakdown.fold(
          0.0,
          (double sum, SpendingBreakdownItem item) => sum + item.amount,
        );
        emit(
          state.copyWith(
            period: event.period,
            spendingBreakdown: uiSpendingBreakdown,
            currentBill: periodTotal,
          ),
        );
      },
      onError: (String errorKey) => state.copyWith(
        status: BillingStatus.failure,
        errorMessage: errorKey,
      ),
    );
  }

  BillingInvoice _mapInvoiceToUiModel(Invoice invoice) {
    // In a real app, fetches related Event/Business names via ID.
    // For now, mapping available fields and hardcoding missing UI placeholders.
    // Preserving "Existing Behavior" means we show something.
    final String dateLabel = invoice.issueDate == null
        ? '2024-01-24'
        : invoice.issueDate!.toIso8601String().split('T').first;
    final String titleLabel = invoice.invoiceNumber ?? invoice.id;
    return BillingInvoice(
      id: titleLabel,
      title: 'Invoice #${invoice.id}', // Placeholder as Invoice lacks title
      locationAddress:
          'Location for ${invoice.eventId}', // Placeholder for address
      clientName: 'Client ${invoice.businessId}', // Placeholder for client name
      date: dateLabel,
      totalAmount: invoice.totalAmount,
      workersCount: 5, // Placeholder count
      totalHours: invoice.workAmount / 25.0, // Estimating hours from amount
      status: invoice.status.name.toUpperCase(),
    );
  }

  List<SpendingBreakdownItem> _mapSpendingItemsToUiModel(
    List<InvoiceItem> items,
  ) {
    final Map<String, SpendingBreakdownItem> aggregation =
        <String, SpendingBreakdownItem>{};

    for (final InvoiceItem item in items) {
      final String category = item.staffId;
      final SpendingBreakdownItem? existing = aggregation[category];
      if (existing != null) {
        aggregation[category] = SpendingBreakdownItem(
          category: category,
          hours: existing.hours + item.workHours.round(),
          amount: existing.amount + item.amount,
        );
      } else {
        aggregation[category] = SpendingBreakdownItem(
          category: category,
          hours: item.workHours.round(),
          amount: item.amount,
        );
      }
    }

    return aggregation.values.toList();
  }
}
