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

/// Implementation of [ClientCreateOrderRepositoryInterface].
///
/// This implementation coordinates data access for order creation by [DataConnectService] from the shared
/// Data Connect package.
///
/// It follows the KROW Clean Architecture by keeping the data layer focused
/// on delegation and data mapping, without business logic.
class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInterface {
  ClientCreateOrderRepositoryImpl({
    required dc.DataConnectService service,
  }) : _service = service;

  final dc.DataConnectService _service;

  @override
  Future<List<domain.OrderType>> getOrderTypes() {
    return Future<List<domain.OrderType>>.value(const <domain.OrderType>[
      domain.OrderType(
        id: 'one-time',
        titleKey: 'client_create_order.types.one_time',
        descriptionKey: 'client_create_order.types.one_time_desc',
      ),

      /// TODO: FEATURE_NOT_YET_IMPLEMENTED
      // domain.OrderType(
      //   id: 'rapid',
      //   titleKey: 'client_create_order.types.rapid',
      //   descriptionKey: 'client_create_order.types.rapid_desc',
      // ),
      // domain.OrderType(
      //   id: 'recurring',
      //   titleKey: 'client_create_order.types.recurring',
      //   descriptionKey: 'client_create_order.types.recurring_desc',
      // ),
      // domain.OrderType(
      //   id: 'permanent',
      //   titleKey: 'client_create_order.types.permanent',
      //   descriptionKey: 'client_create_order.types.permanent_desc',
      // ),
    ]);
  }

  @override
  Future<void> createOneTimeOrder(domain.OneTimeOrder order) async {
    return _service.run(() async {
      final String businessId = await _service.getBusinessId();
      final String? vendorId = order.vendorId;
      if (vendorId == null || vendorId.isEmpty) {
        throw Exception('Vendor is missing.');
      }
      final domain.OneTimeOrderHubDetails? hub = order.hub;
      if (hub == null || hub.id.isEmpty) {
        throw Exception('Hub is missing.');
      }

      final DateTime orderDateOnly = DateTime(
        order.date.year,
        order.date.month,
        order.date.day,
      );
      final fdc.Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
      final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult =
          await _service.connector
              .createOrder(
                businessId: businessId,
                orderType: dc.OrderType.ONE_TIME,
                teamHubId: hub.id,
              )
              .vendorId(vendorId)
              .eventName(order.eventName)
              .status(dc.OrderStatus.POSTED)
              .date(orderTimestamp)
              .execute();

      final String orderId = orderResult.data.order_insert.id;

      final int workersNeeded = order.positions.fold<int>(
        0,
        (int sum, domain.OneTimeOrderPosition position) => sum + position.count,
      );
      final String shiftTitle = 'Shift 1 ${_formatDate(order.date)}';
      final double shiftCost = _calculateShiftCost(order);

      final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult =
          await _service.connector
              .createShift(title: shiftTitle, orderId: orderId)
              .date(orderTimestamp)
              .location(hub.name)
              .locationAddress(hub.address)
              .latitude(hub.latitude)
              .longitude(hub.longitude)
              .placeId(hub.placeId)
              .city(hub.city)
              .state(hub.state)
              .street(hub.street)
              .country(hub.country)
              .status(dc.ShiftStatus.PENDING)
              .workersNeeded(workersNeeded)
              .filled(0)
              .durationDays(1)
              .cost(shiftCost)
              .execute();

      final String shiftId = shiftResult.data.shift_insert.id;

      for (final domain.OneTimeOrderPosition position in order.positions) {
        final DateTime start = _parseTime(order.date, position.startTime);
        final DateTime end = _parseTime(order.date, position.endTime);
        final DateTime normalizedEnd = end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
        final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
        final double rate = order.roleRates[position.role] ?? 0;
        final double totalValue = rate * hours * position.count;

        await _service.connector
            .createShiftRole(
              shiftId: shiftId,
              roleId: position.role,
              count: position.count,
            )
            .startTime(_service.toTimestamp(start))
            .endTime(_service.toTimestamp(normalizedEnd))
            .hours(hours)
            .breakType(_breakDurationFromValue(position.lunchBreak))
            .isBreakPaid(_isBreakPaid(position.lunchBreak))
            .totalValue(totalValue)
            .execute();
      }

      await _service.connector
          .updateOrder(id: orderId, teamHubId: hub.id)
          .shifts(fdc.AnyValue(<String>[shiftId]))
          .execute();
    });
  }

  @override
  Future<void> createRapidOrder(String description) async {
    // TO-DO: connect IA and return array with the information.
    throw UnimplementedError('Rapid order IA is not connected yet.');
  }

  double _calculateShiftCost(domain.OneTimeOrder order) {
    double total = 0;
    for (final domain.OneTimeOrderPosition position in order.positions) {
      final DateTime start = _parseTime(order.date, position.startTime);
      final DateTime end = _parseTime(order.date, position.endTime);
      final DateTime normalizedEnd =
          end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
      final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
      final double rate = order.roleRates[position.role] ?? 0;
      total += rate * hours * position.count;
    }
    return total;
  }

  dc.BreakDuration _breakDurationFromValue(String value) {
    switch (value) {
      case 'MIN_10':
        return dc.BreakDuration.MIN_10;
      case 'MIN_15':
        return dc.BreakDuration.MIN_15;
      case 'MIN_30':
        return dc.BreakDuration.MIN_30;
      case 'MIN_45':
        return dc.BreakDuration.MIN_45;
      case 'MIN_60':
        return dc.BreakDuration.MIN_60;
      default:
        return dc.BreakDuration.NO_BREAK;
    }
  }

  bool _isBreakPaid(String value) {
    return value == 'MIN_10' || value == 'MIN_15';
  }

  DateTime _parseTime(DateTime date, String time) {
    if (time.trim().isEmpty) {
      throw Exception('Shift time is missing.');
    }

    DateTime parsed;
    try {
      parsed = DateFormat.jm().parse(time);
    } catch (_) {
      parsed = DateFormat.Hm().parse(time);
    }

    return DateTime(
      date.year,
      date.month,
      date.day,
      parsed.hour,
      parsed.minute,
    );
  }

  String _formatDate(DateTime dateTime) {
    final String year = dateTime.year.toString().padLeft(4, '0');
    final String month = dateTime.month.toString().padLeft(2, '0');
    final String day = dateTime.day.toString().padLeft(2, '0');
    return '$year-$month-$day';
  }
}
