import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as data_connect;
import 'package:krow_domain/krow_domain.dart';
import '../../domain/models/billing_period.dart';
import '../../domain/repositories/billing_repository.dart';

/// Implementation of [BillingRepository] in the Data layer.
///
/// This class is responsible for retrieving billing data from the [FinancialRepositoryMock]
/// (which represents the Data Connect layer) and mapping it to Domain entities.
///
/// It strictly adheres to the Clean Architecture data layer responsibilities:
/// - No business logic (except necessary data transformation/filtering).
/// - Delegates to data sources.
class BillingRepositoryImpl implements BillingRepository {
  /// Creates a [BillingRepositoryImpl].
  ///
  /// Requires the [financialRepository] to fetch financial data.
  BillingRepositoryImpl({
    required data_connect.FinancialRepositoryMock financialRepository,
    required data_connect.ExampleConnector dataConnect,
  })  : _financialRepository = financialRepository,
        _dataConnect = dataConnect;

  final data_connect.FinancialRepositoryMock _financialRepository;
  final data_connect.ExampleConnector _dataConnect;

  /// Fetches the current bill amount by aggregating open invoices.
  @override
  Future<double> getCurrentBillAmount() async {
    // In a real app, this might be an aggregate query.
    // Simulating fetching invoices and summing up.
    final List<Invoice> invoices = await _financialRepository.getInvoices(
      'current_business',
    );
    return invoices
        .where((Invoice i) => i.status == InvoiceStatus.open)
        .fold<double>(
          0.0,
          (double sum, Invoice item) => sum + item.totalAmount,
        );
  }

  /// Fetches the history of paid invoices.
  @override
  Future<List<Invoice>> getInvoiceHistory() async {
    final String? businessId =
        data_connect.ClientSessionStore.instance.session?.business?.id;
    if (businessId == null || businessId.isEmpty) {
      return <Invoice>[];
    }

    final fdc.QueryResult<data_connect.ListInvoicesByBusinessIdData,
        data_connect.ListInvoicesByBusinessIdVariables> result =
        await _dataConnect
            .listInvoicesByBusinessId(
              businessId: businessId,
            )
            .limit(10)
            .execute();

    return result.data.invoices.map(_mapInvoice).toList();
  }

  /// Fetches pending invoices (Open or Disputed).
  @override
  Future<List<Invoice>> getPendingInvoices() async {
    final List<Invoice> invoices = await _financialRepository.getInvoices(
      'current_business',
    );
    return invoices
        .where(
          (Invoice i) =>
              i.status == InvoiceStatus.open ||
              i.status == InvoiceStatus.disputed,
        )
        .toList();
  }

  /// Fetches the estimated savings amount.
  @override
  Future<double> getSavingsAmount() async {
    // Simulating savings calculation (e.g., comparing to market rates).
    await Future<void>.delayed(const Duration(milliseconds: 0));
    return 0.0;
  }

  /// Fetches the breakdown of spending.
  @override
  Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period) async {
    final String? businessId =
        data_connect.ClientSessionStore.instance.session?.business?.id;
    if (businessId == null || businessId.isEmpty) {
      return <InvoiceItem>[];
    }

    final DateTime now = DateTime.now();
    final DateTime start;
    final DateTime end;
    if (period == BillingPeriod.week) {
      final int daysFromMonday = now.weekday - DateTime.monday;
      final DateTime monday = DateTime(
        now.year,
        now.month,
        now.day,
      ).subtract(Duration(days: daysFromMonday));
      start = DateTime(monday.year, monday.month, monday.day);
      end = DateTime(monday.year, monday.month, monday.day + 6, 23, 59, 59, 999);
    } else {
      start = DateTime(now.year, now.month, 1);
      end = DateTime(now.year, now.month + 1, 0, 23, 59, 59, 999);
    }

    final fdc.QueryResult<
        data_connect.ListShiftRolesByBusinessAndDatesSummaryData,
        data_connect.ListShiftRolesByBusinessAndDatesSummaryVariables> result =
        await _dataConnect
            .listShiftRolesByBusinessAndDatesSummary(
              businessId: businessId,
              start: _toTimestamp(start),
              end: _toTimestamp(end),
            )
            .execute();

    final List<data_connect.ListShiftRolesByBusinessAndDatesSummaryShiftRoles>
        shiftRoles = result.data.shiftRoles;
    if (shiftRoles.isEmpty) {
      return <InvoiceItem>[];
    }

    final Map<String, _RoleSummary> summary = <String, _RoleSummary>{};
    for (final data_connect.ListShiftRolesByBusinessAndDatesSummaryShiftRoles role
        in shiftRoles) {
      final String roleId = role.roleId;
      final String roleName = role.role.name;
      final double hours = role.hours ?? 0.0;
      final double totalValue = role.totalValue ?? 0.0;
      final _RoleSummary? existing = summary[roleId];
      if (existing == null) {
        summary[roleId] = _RoleSummary(
          roleId: roleId,
          roleName: roleName,
          totalHours: hours,
          totalValue: totalValue,
        );
      } else {
        summary[roleId] = existing.copyWith(
          totalHours: existing.totalHours + hours,
          totalValue: existing.totalValue + totalValue,
        );
      }
    }

    return summary.values
        .map(
          (_RoleSummary item) => InvoiceItem(
            id: item.roleId,
            invoiceId: item.roleId,
            staffId: item.roleName,
            workHours: item.totalHours,
            rate: item.totalHours > 0 ? item.totalValue / item.totalHours : 0,
            amount: item.totalValue,
          ),
        )
        .toList();
  }

  fdc.Timestamp _toTimestamp(DateTime dateTime) {
    final DateTime utc = dateTime.toUtc();
    final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
    final int nanoseconds =
        (utc.millisecondsSinceEpoch % 1000) * 1000000;
    return fdc.Timestamp(nanoseconds, seconds);
  }

  Invoice _mapInvoice(data_connect.ListInvoicesByBusinessIdInvoices invoice) {
    return Invoice(
      id: invoice.id,
      eventId: invoice.orderId,
      businessId: invoice.businessId,
      status: _mapInvoiceStatus(invoice.status),
      totalAmount: invoice.amount,
      workAmount: invoice.amount,
      addonsAmount: invoice.otherCharges ?? 0,
      invoiceNumber: invoice.invoiceNumber,
      issueDate: invoice.issueDate.toDateTime(),
    );
  }

  InvoiceStatus _mapInvoiceStatus(
    data_connect.EnumValue<data_connect.InvoiceStatus> status,
  ) {
    if (status is data_connect.Known<data_connect.InvoiceStatus>) {
      switch (status.value) {
        case data_connect.InvoiceStatus.PAID:
          return InvoiceStatus.paid;
        case data_connect.InvoiceStatus.OVERDUE:
          return InvoiceStatus.overdue;
        case data_connect.InvoiceStatus.DISPUTED:
          return InvoiceStatus.disputed;
        case data_connect.InvoiceStatus.APPROVED:
          return InvoiceStatus.verified;
        case data_connect.InvoiceStatus.PENDING_REVIEW:
        case data_connect.InvoiceStatus.PENDING:
        case data_connect.InvoiceStatus.DRAFT:
          return InvoiceStatus.open;
      }
    }
    return InvoiceStatus.open;
  }
}

class _RoleSummary {
  const _RoleSummary({
    required this.roleId,
    required this.roleName,
    required this.totalHours,
    required this.totalValue,
  });

  final String roleId;
  final String roleName;
  final double totalHours;
  final double totalValue;

  _RoleSummary copyWith({
    double? totalHours,
    double? totalValue,
  }) {
    return _RoleSummary(
      roleId: roleId,
      roleName: roleName,
      totalHours: totalHours ?? this.totalHours,
      totalValue: totalValue ?? this.totalValue,
    );
  }
}
