import '../domain/inventory_adjustment_result.dart';
import '../domain/inventory_item.dart';
import '../domain/inventory_repository.dart';
import '../domain/inventory_sync_status.dart';

/// Result of a full inventory sync operation.
class InventorySyncResult {
  /// Number of items successfully synced.
  final int syncedCount;

  /// Number of items that failed to sync.
  final int failedCount;

  /// Number of items skipped (no WooCommerce product ID).
  final int skippedCount;

  /// Per-item results keyed by inventory item ID.
  final Map<String, InventoryAdjustmentResult> itemResults;

  const InventorySyncResult({
    required this.syncedCount,
    required this.failedCount,
    required this.skippedCount,
    required this.itemResults,
  });

  /// Whether all syncable items were synced successfully.
  bool get allSucceeded => failedCount == 0;

  /// Total number of items processed (synced + failed, not skipped).
  int get totalProcessed => syncedCount + failedCount;
}

/// Orchestrates comparing local inventory quantities against WooCommerce
/// stock levels and resolving differences.
///
/// The sync strategy is **remote-wins**: the WooCommerce stock quantity is
/// treated as authoritative and is written into the local inventory.
///
/// Usage:
/// ```dart
/// final service = InventorySyncService(
///   inventoryRepository: myRepo,
///   fetchRemoteStock: (productId) async {
///     final data = await apiClient.getProductStock(productId.toString());
///     return data['stock_quantity'] as int? ?? 0;
///   },
///   pushLocalStock: (productId, quantity) async {
///     await apiClient.updateProductStock(productId.toString(), quantity);
///   },
/// );
/// final result = await service.syncAll();
/// ```
class InventorySyncService {
  final InventoryRepository _inventoryRepository;

  /// Callback that fetches the remote stock quantity for a WooCommerce product.
  ///
  /// Receives the WooCommerce product ID and returns the current stock quantity.
  final Future<int> Function(int productId) fetchRemoteStock;

  /// Optional callback to push a local quantity to WooCommerce.
  ///
  /// When provided, items whose local quantity differs from remote will be
  /// pushed to WooCommerce after the local record is updated.
  /// When `null`, the sync is pull-only (remote → local).
  final Future<void> Function(int productId, int quantity)? pushLocalStock;

  InventorySyncService({
    required InventoryRepository inventoryRepository,
    required this.fetchRemoteStock,
    this.pushLocalStock,
  }) : _inventoryRepository = inventoryRepository;

  /// Syncs all inventory items that have a [InventoryItem.wooCommerceProductId].
  ///
  /// Items without a WooCommerce product ID are skipped.
  /// Returns an [InventorySyncResult] summarising the outcome.
  Future<InventorySyncResult> syncAll() async {
    final items = await _inventoryRepository.getInventoryItems();
    final syncable = items.where((i) => i.wooCommerceProductId != null).toList();
    final skipped = items.length - syncable.length;

    final results = <String, InventoryAdjustmentResult>{};
    var synced = 0;
    var failed = 0;

    for (final item in syncable) {
      final result = await _syncItem(item);
      results[item.id] = result;
      if (result.success) {
        synced++;
      } else {
        failed++;
      }
    }

    return InventorySyncResult(
      syncedCount: synced,
      failedCount: failed,
      skippedCount: skipped,
      itemResults: Map.unmodifiable(results),
    );
  }

  /// Syncs a single inventory item by its local [id].
  ///
  /// Fetches the remote stock quantity and calls [InventoryRepository.syncWithRemote].
  /// Returns an [InventoryAdjustmentResult] reflecting the outcome.
  Future<InventoryAdjustmentResult> syncItem(String id) async {
    final items = await _inventoryRepository.getInventoryItems();
    final item = items.where((i) => i.id == id).firstOrNull;

    if (item == null) {
      return const InventoryAdjustmentResult.failure('Item not found');
    }

    return _syncItem(item);
  }

  // ── Private helpers ────────────────────────────────────────────────────────

  Future<InventoryAdjustmentResult> _syncItem(InventoryItem item) async {
    final productId = item.wooCommerceProductId;
    if (productId == null) {
      return const InventoryAdjustmentResult.failure('Item has no WooCommerce product ID');
    }

    try {
      final remoteQty = await fetchRemoteStock(productId);
      final result = await _inventoryRepository.syncWithRemote(
        item.id,
        remoteQty,
        'WooCommerce sync',
      );

      // If push is configured and sync succeeded, push local quantity back
      // (useful when local was the source of truth for a manual adjustment).
      if (result.success && pushLocalStock != null) {
        try {
          await pushLocalStock!(productId, remoteQty);
        } catch (_) {
          // Push failure is non-fatal — local record is already updated.
        }
      }

      return result;
    } catch (e) {
      return InventoryAdjustmentResult.failure('Sync failed: $e');
    }
  }

  /// Compares local and remote quantities for a single item without writing.
  ///
  /// Returns `null` if the item has no WooCommerce product ID.
  /// Returns a [StockDifference] describing the discrepancy (or lack thereof).
  Future<StockDifference?> checkDifference(String id) async {
    final items = await _inventoryRepository.getInventoryItems();
    final item = items.where((i) => i.id == id).firstOrNull;

    if (item == null || item.wooCommerceProductId == null) return null;

    try {
      final remoteQty = await fetchRemoteStock(item.wooCommerceProductId!);
      return StockDifference(
        itemId: item.id,
        localQuantity: item.quantityOnHand,
        remoteQuantity: remoteQty,
        syncStatus: item.syncStatus,
      );
    } catch (e) {
      return StockDifference(
        itemId: item.id,
        localQuantity: item.quantityOnHand,
        remoteQuantity: null,
        syncStatus: InventorySyncStatus.error,
        errorMessage: e.toString(),
      );
    }
  }
}

/// Describes the difference between local and remote stock for a single item.
class StockDifference {
  final String itemId;
  final int localQuantity;
  final int? remoteQuantity;
  final InventorySyncStatus syncStatus;
  final String? errorMessage;

  const StockDifference({
    required this.itemId,
    required this.localQuantity,
    required this.remoteQuantity,
    required this.syncStatus,
    this.errorMessage,
  });

  /// Whether local and remote quantities match.
  bool get isInSync => remoteQuantity != null && localQuantity == remoteQuantity;

  /// Whether there is a discrepancy between local and remote.
  bool get hasDiscrepancy => remoteQuantity != null && localQuantity != remoteQuantity;
}
