import 'package:firebase_auth/firebase_auth.dart' as firebase;
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 delegating
/// to the [OrderRepositoryMock] and [ExampleConnector] 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 firebase.FirebaseAuth firebaseAuth,
    required dc.ExampleConnector dataConnect,
  }) : _firebaseAuth = firebaseAuth,
       _dataConnect = dataConnect;

  final firebase.FirebaseAuth _firebaseAuth;
  final dc.ExampleConnector _dataConnect;

  @override
  Future<List<domain.OrderType>> getOrderTypes() {
    return Future.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 {
    final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
    if (businessId == null || businessId.isEmpty) {
      await _firebaseAuth.signOut();
      throw Exception('Business is missing. Please sign in again.');
    }
    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 = _toTimestamp(orderDateOnly);
    final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult = await _dataConnect
        .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;
    if (orderId == null) {
      throw Exception('Order creation failed.');
    }

    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 _dataConnect
        .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;
    if (shiftId == null) {
      throw Exception('Shift creation failed.');
    }

    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;

      print(
        'CreateOneTimeOrder shiftRole: start=${start.toIso8601String()} end=${normalizedEnd.toIso8601String()}',
      );

      await _dataConnect
          .createShiftRole(
            shiftId: shiftId,
            roleId: position.role,
            count: position.count,
          )
          .startTime(_toTimestamp(start))
          .endTime(_toTimestamp(normalizedEnd))
          .hours(hours)
          .breakType(_breakDurationFromValue(position.lunchBreak))
          .totalValue(totalValue)
          .execute();
    }

    await _dataConnect
        .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_15':
        return dc.BreakDuration.MIN_15;
      case 'MIN_30':
        return dc.BreakDuration.MIN_30;
      default:
        return dc.BreakDuration.NO_BREAK;
    }
  }

  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,
    );
  }

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

  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';
  }
}
