import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/coverage_repository.dart';

/// Implementation of [CoverageRepository] in the Data layer.
///
/// This class provides mock data for the coverage feature.
/// In a production environment, this would delegate to `packages/data_connect`
/// for real data access (e.g., Firebase Data Connect, REST API).
///
/// It strictly adheres to the Clean Architecture data layer responsibilities:
/// - No business logic (except necessary data transformation).
/// - Delegates to data sources (currently mock data, will be `data_connect`).
/// - Returns domain entities from `domain/ui_entities`.
class CoverageRepositoryImpl implements CoverageRepository {
  /// Creates a [CoverageRepositoryImpl].
  CoverageRepositoryImpl({required dc.DataConnectService service}) : _service = service;

  final dc.DataConnectService _service;

  /// Fetches shifts for a specific date.
  @override
  Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
    return _service.run(() async {
      final String businessId = await _service.getBusinessId();

      final DateTime start = DateTime(date.year, date.month, date.day);
      final DateTime end = DateTime(date.year, date.month, date.day, 23, 59, 59, 999);

      final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData,
              dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
          await _service.connector
              .listShiftRolesByBusinessAndDateRange(
                businessId: businessId,
                start: _service.toTimestamp(start),
                end: _service.toTimestamp(end),
              )
              .execute();

      final fdc.QueryResult<dc.ListStaffsApplicationsByBusinessForDayData,
              dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult =
          await _service.connector
              .listStaffsApplicationsByBusinessForDay(
                businessId: businessId,
                dayStart: _service.toTimestamp(start),
                dayEnd: _service.toTimestamp(end),
              )
              .execute();

      return _mapCoverageShifts(
        shiftRolesResult.data.shiftRoles,
        applicationsResult.data.applications,
        date,
      );
    });
  }

  /// Fetches coverage statistics for a specific date.
  @override
  Future<CoverageStats> getCoverageStats({required DateTime date}) async {
    // Get shifts for the date
    final List<CoverageShift> shifts = await getShiftsForDate(date: date);

    // Calculate statistics
    final int totalNeeded = shifts.fold<int>(
      0,
      (int sum, CoverageShift shift) => sum + shift.workersNeeded,
    );

    final List<CoverageWorker> allWorkers =
        shifts.expand((CoverageShift shift) => shift.workers).toList();
    final int totalConfirmed = allWorkers.length;
    final int checkedIn = allWorkers
        .where((CoverageWorker w) => w.status == CoverageWorkerStatus.checkedIn)
        .length;
    final int enRoute = allWorkers
        .where((CoverageWorker w) =>
            w.status == CoverageWorkerStatus.confirmed && w.checkInTime == null)
        .length;
    final int late = allWorkers
        .where((CoverageWorker w) => w.status == CoverageWorkerStatus.late)
        .length;

    return CoverageStats(
      totalNeeded: totalNeeded,
      totalConfirmed: totalConfirmed,
      checkedIn: checkedIn,
      enRoute: enRoute,
      late: late,
    );
  }

  List<CoverageShift> _mapCoverageShifts(
    List<dc.ListShiftRolesByBusinessAndDateRangeShiftRoles> shiftRoles,
    List<dc.ListStaffsApplicationsByBusinessForDayApplications> applications,
    DateTime date,
  ) {
    if (shiftRoles.isEmpty && applications.isEmpty) {
      return <CoverageShift>[];
    }

    final Map<String, _CoverageGroup> groups = <String, _CoverageGroup>{};
    for (final dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole
        in shiftRoles) {
      final String key = '${shiftRole.shiftId}:${shiftRole.roleId}';
      groups[key] = _CoverageGroup(
        shiftId: shiftRole.shiftId,
        roleId: shiftRole.roleId,
        title: shiftRole.role.name,
        location: shiftRole.shift.location ?? shiftRole.shift.locationAddress ?? '',
        startTime: _formatTime(shiftRole.startTime) ?? '00:00',
        workersNeeded: shiftRole.count,
        date: shiftRole.shift.date?.toDateTime() ?? date,
        workers: <CoverageWorker>[],
      );
    }

    for (final dc.ListStaffsApplicationsByBusinessForDayApplications app
        in applications) {
      final String key = '${app.shiftId}:${app.roleId}';
      final _CoverageGroup existing = groups[key] ??
          _CoverageGroup(
            shiftId: app.shiftId,
            roleId: app.roleId,
            title: app.shiftRole.role.name,
            location: app.shiftRole.shift.location ??
                app.shiftRole.shift.locationAddress ??
                '',
            startTime: _formatTime(app.shiftRole.startTime) ?? '00:00',
            workersNeeded: app.shiftRole.count,
            date: app.shiftRole.shift.date?.toDateTime() ?? date,
            workers: <CoverageWorker>[],
          );

      existing.workers.add(
        CoverageWorker(
          name: app.staff.fullName,
          status: _mapWorkerStatus(app.status),
          checkInTime: _formatTime(app.checkInTime),
        ),
      );
      groups[key] = existing;
    }

    return groups.values
        .map(
          (_CoverageGroup group) => CoverageShift(
            id: '${group.shiftId}:${group.roleId}',
            title: group.title,
            location: group.location,
            startTime: group.startTime,
            workersNeeded: group.workersNeeded,
            date: group.date,
            workers: group.workers,
          ),
        )
        .toList();
  }

  CoverageWorkerStatus _mapWorkerStatus(
    dc.EnumValue<dc.ApplicationStatus> status,
  ) {
    if (status is dc.Known<dc.ApplicationStatus>) {
      switch (status.value) {
        case dc.ApplicationStatus.PENDING:
          return CoverageWorkerStatus.pending;
        case dc.ApplicationStatus.REJECTED:
          return CoverageWorkerStatus.rejected;
        case dc.ApplicationStatus.CONFIRMED:
          return CoverageWorkerStatus.confirmed;
        case dc.ApplicationStatus.CHECKED_IN:
          return CoverageWorkerStatus.checkedIn;
        case dc.ApplicationStatus.CHECKED_OUT:
          return CoverageWorkerStatus.checkedOut;
        case dc.ApplicationStatus.LATE:
          return CoverageWorkerStatus.late;
        case dc.ApplicationStatus.NO_SHOW:
          return CoverageWorkerStatus.noShow;
        case dc.ApplicationStatus.COMPLETED:
          return CoverageWorkerStatus.completed;
      }
    }
    return CoverageWorkerStatus.pending;
  }

  String? _formatTime(fdc.Timestamp? timestamp) {
    if (timestamp == null) {
      return null;
    }
    final DateTime date = timestamp.toDateTime().toLocal();
    final String hour = date.hour.toString().padLeft(2, '0');
    final String minute = date.minute.toString().padLeft(2, '0');
    return '$hour:$minute';
  }
}

class _CoverageGroup {
  _CoverageGroup({
    required this.shiftId,
    required this.roleId,
    required this.title,
    required this.location,
    required this.startTime,
    required this.workersNeeded,
    required this.date,
    required this.workers,
  });

  final String shiftId;
  final String roleId;
  final String title;
  final String location;
  final String startTime;
  final int workersNeeded;
  final DateTime date;
  final List<CoverageWorker> workers;
}
