import 'dart:async';

import 'package:uuid/uuid.dart';

import '../models/shipment_models.dart';
import 'business_calendar_mapper.dart';
import 'sales_order_repository.dart';
import 'shipment_repository.dart';

class ShipmentLineInput {
  const ShipmentLineInput({
    required this.description,
    required this.quantity,
    this.orderItemId,
  });

  final String description;
  final int quantity;
  final String? orderItemId;
}

class ShipmentService {
  ShipmentService({
    ShipmentRepository? shipmentRepository,
    SalesOrderRepository? orderRepository,
    BusinessCalendarMapper? calendarMapper,
  })  : _shipmentRepository = shipmentRepository ?? ShipmentRepository(),
        _orderRepository = orderRepository ?? SalesOrderRepository(),
        _calendarMapper = calendarMapper ?? BusinessCalendarMapper();

  final ShipmentRepository _shipmentRepository;
  final SalesOrderRepository _orderRepository;
  final BusinessCalendarMapper _calendarMapper;
  final Uuid _uuid = const Uuid();

  static const Map<ShipmentStatus, List<ShipmentStatus>> _transitions = {
    ShipmentStatus.pending: [ShipmentStatus.picking, ShipmentStatus.cancelled],
    ShipmentStatus.picking: [ShipmentStatus.ready, ShipmentStatus.cancelled],
    ShipmentStatus.ready: [ShipmentStatus.shipped, ShipmentStatus.cancelled],
    ShipmentStatus.shipped: [ShipmentStatus.delivered],
    ShipmentStatus.delivered: [],
    ShipmentStatus.cancelled: [],
  };

  Future<List<Shipment>> fetchShipments({ShipmentStatus? status}) {
    return _shipmentRepository.fetchShipments(status: status);
  }

  Future<Shipment> createShipment({
    String? orderId,
    String? orderNumberSnapshot,
    String? customerNameSnapshot,
    List<ShipmentLineInput> lines = const [],
    DateTime? scheduledShipDate,
    DateTime? actualShipDate,
    String? carrierName,
    String? trackingNumber,
    String? trackingUrl,
    String? labelPdfPath,
    String? notes,
  }) async {
    String? resolvedOrderId = orderId;
    String? resolvedOrderNumber = orderNumberSnapshot;
    String? resolvedCustomerName = customerNameSnapshot;

    if (resolvedOrderId != null && (resolvedOrderNumber == null || resolvedCustomerName == null)) {
      final order = await _orderRepository.findById(resolvedOrderId);
      if (order != null) {
        resolvedOrderNumber = order.orderNumber ?? order.id.substring(0, 6);
        resolvedCustomerName = order.customerNameSnapshot;
      }
    }

    resolvedCustomerName ??= '未設定';

    final now = DateTime.now();
    final shipmentId = _uuid.v4();
    final shipment = Shipment(
      id: shipmentId,
      orderId: resolvedOrderId,
      orderNumberSnapshot: resolvedOrderNumber,
      customerNameSnapshot: resolvedCustomerName,
      scheduledShipDate: scheduledShipDate,
      actualShipDate: actualShipDate,
      status: ShipmentStatus.pending,
      carrierName: carrierName,
      trackingNumber: trackingNumber,
      trackingUrl: trackingUrl,
      labelPdfPath: labelPdfPath,
      notes: notes,
      pickingCompletedAt: null,
      createdAt: now,
      updatedAt: now,
      items: _buildItems(shipmentId, lines),
    );
    await _shipmentRepository.upsertShipment(shipment);
    unawaited(_calendarMapper.syncShipment(shipment));
    return shipment;
  }

  Future<Shipment> updateShipment(
    Shipment shipment, {
    List<ShipmentLineInput>? replacedLines,
    DateTime? scheduledShipDate,
    DateTime? actualShipDate,
    String? carrierName,
    String? trackingNumber,
    String? trackingUrl,
    String? labelPdfPath,
    String? notes,
  }) async {
    final updated = shipment.copyWith(
      scheduledShipDate: scheduledShipDate ?? shipment.scheduledShipDate,
      actualShipDate: actualShipDate ?? shipment.actualShipDate,
      carrierName: carrierName ?? shipment.carrierName,
      trackingNumber: trackingNumber ?? shipment.trackingNumber,
      trackingUrl: trackingUrl ?? shipment.trackingUrl,
      labelPdfPath: labelPdfPath ?? shipment.labelPdfPath,
      notes: notes ?? shipment.notes,
      updatedAt: DateTime.now(),
      items: replacedLines != null ? _buildItems(shipment.id, replacedLines) : shipment.items,
    );
    await _shipmentRepository.upsertShipment(updated);
    unawaited(_calendarMapper.syncShipment(updated));
    return updated;
  }

  Future<Shipment> transitionStatus(String shipmentId, ShipmentStatus nextStatus, {bool force = false}) async {
    final shipment = await _shipmentRepository.findById(shipmentId);
    if (shipment == null) {
      throw StateError('shipment not found: $shipmentId');
    }
    if (!force && !_canTransition(shipment.status, nextStatus)) {
      throw StateError('invalid transition ${shipment.status.name} -> ${nextStatus.name}');
    }
    final now = DateTime.now();
    final updated = shipment.copyWith(
      status: nextStatus,
      updatedAt: now,
      actualShipDate: nextStatus == ShipmentStatus.shipped || nextStatus == ShipmentStatus.delivered
          ? (shipment.actualShipDate ?? now)
          : shipment.actualShipDate,
      pickingCompletedAt: nextStatus == ShipmentStatus.ready ? (shipment.pickingCompletedAt ?? now) : shipment.pickingCompletedAt,
    );
    await _shipmentRepository.upsertShipment(updated);
    unawaited(_calendarMapper.syncShipment(updated));
    return updated;
  }

  Future<Shipment> advanceStatus(String shipmentId) async {
    final shipment = await _shipmentRepository.findById(shipmentId);
    if (shipment == null) {
      throw StateError('shipment not found: $shipmentId');
    }
    final candidates = _transitions[shipment.status];
    if (candidates == null || candidates.isEmpty) {
      return shipment;
    }
    return transitionStatus(shipmentId, candidates.first);
  }

  List<ShipmentStatus> nextStatuses(ShipmentStatus current) {
    return List.unmodifiable(_transitions[current] ?? const []);
  }

  bool _canTransition(ShipmentStatus current, ShipmentStatus next) {
    final allowed = _transitions[current];
    return allowed?.contains(next) ?? false;
  }

  List<ShipmentItem> _buildItems(String shipmentId, List<ShipmentLineInput> lines) {
    return lines.asMap().entries.map((entry) {
      final line = entry.value;
      return ShipmentItem(
        id: _uuid.v4(),
        shipmentId: shipmentId,
        orderItemId: line.orderItemId,
        description: line.description,
        quantity: line.quantity,
      );
    }).toList();
  }
}
