/// Cart Provider
///
/// State management for shopping cart using Riverpod with API integration.
library;

import 'dart:async';

import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:worker/features/cart/presentation/providers/cart_data_providers.dart';
import 'package:worker/features/cart/presentation/providers/cart_state.dart';
import 'package:worker/features/products/domain/entities/product.dart';

part 'cart_provider.g.dart';

/// Cart Notifier
///
/// Manages cart state with API integration:
/// - Adding/removing items (syncs with API)
/// - Updating quantities (syncs with API with 3s debounce)
/// - Loading cart from API via initialize()
/// - Local-only operations: selection, warehouse, calculations
/// - keepAlive: true to maintain cart state across navigation
@Riverpod(keepAlive: true)
class Cart extends _$Cart {
  /// Debounce timer for quantity updates (3 seconds)
  Timer? _debounceTimer;

  /// Map to track pending quantity updates (productId -> quantity)
  final Map<String, double> _pendingQuantityUpdates = {};

  @override
  CartState build() {
    // Cancel debounce timer when provider is disposed
    ref.onDispose(() {
      _debounceTimer?.cancel();
    });

    // Start with initial state
    // Call initialize() from UI to load from API (from HomePage)
    return CartState.initial().copyWith(
      memberTier: 'Diamond',
      memberDiscountPercent: 15.0,
    );
  }

  /// Initialize cart by loading from API
  ///
  /// Call this ONCE from HomePage on app startup.
  /// Cart API returns product details, no need to fetch each product separately.
  Future<void> initialize() async {
    // Skip if already loaded
    if (state.items.isNotEmpty) return;

    final repository = await ref.read(cartRepositoryProvider.future);

    // Set loading state
    state = state.copyWith(isLoading: true, errorMessage: null);

    try {
      // Load cart items from API (with Hive fallback)
      // Cart API returns: item_code, item_name, image, conversion_of_sm, quantity, amount
      final cartItems = await repository.getCartItems();

      // Get member tier from user profile
      // TODO: Replace with actual user tier from auth
      const memberTier = 'Diamond';
      const memberDiscountPercent = 15.0;

      // Convert CartItem entities to CartItemData for UI
      // Use product data from cart API directly - no need to fetch each product
      final items = <CartItemData>[];
      final selectedItems = <String, bool>{};

      for (final cartItem in cartItems) {
        // Create minimal Product from cart item data (no need to fetch from API)
        final now = DateTime.now();
        final product = Product(
          productId: cartItem.productId,
          name: cartItem.itemName ?? cartItem.productId,
          basePrice: cartItem.unitPrice,
          images: cartItem.image != null ? [cartItem.image!] : [],
          thumbnail: cartItem.image ?? '',
          imageCaptions: const {},
          specifications: const {},
          conversionOfSm: cartItem.conversionOfSm,
          erpnextItemCode: cartItem.productId,
          isActive: true,
          isFeatured: false,
          createdAt: now,
          updatedAt: now,
        );

        // Calculate conversion for this item
        final converted = _calculateConversion(
          cartItem.quantity,
          product.conversionOfSm,
        );

        // Create CartItemData with product info from cart API
        items.add(
          CartItemData(
            product: product,
            quantity: cartItem.quantity,
            quantityConverted: converted.convertedQuantity,
            boxes: converted.boxes,
          ),
        );

        // Initialize as not selected by default
        selectedItems[product.productId] = false;
      }

      final newState = CartState(
        items: items,
        selectedItems: selectedItems,
        selectedWarehouse: 'Kho Hà Nội - Nguyễn Trãi',
        discountCode: null,
        discountCodeApplied: false,
        memberTier: memberTier,
        memberDiscountPercent: memberDiscountPercent,
        subtotal: 0.0,
        memberDiscount: 0.0,
        shippingFee: 0.0,
        total: 0.0,
        isLoading: false,
      );

      // Recalculate totals
      state = _recalculateTotal(newState);
    } catch (e) {
      // If loading fails, keep current state but show error
      state = state.copyWith(
        isLoading: false,
        errorMessage: 'Failed to load cart: ${e.toString()}',
      );
    }
  }

  /// Add product to cart (API + Local)
  Future<void> addToCart(Product product, {double quantity = 1.0}) async {
    final repository = await ref.read(cartRepositoryProvider.future);
    final currentState = state;

    // Set loading state
    state = currentState.copyWith(isLoading: true, errorMessage: null);

    try {
      // Check if item exists
      final existingItemIndex = currentState.items.indexWhere(
        (item) => item.product.productId == product.productId,
      );

      if (existingItemIndex >= 0) {
        // Update quantity if item already exists
        final newQuantity = currentState.items[existingItemIndex].quantity + quantity;
        await updateQuantity(product.productId, newQuantity);
      } else {
        // Add new item via API
        await repository.addToCart(
          itemIds: [product.erpnextItemCode ?? product.productId],
          quantities: [quantity],
          prices: [product.basePrice],
          conversionFactors: [product.conversionOfSm],
        );

        // Calculate conversion
        final converted = _calculateConversion(quantity, product.conversionOfSm);
        final newItem = CartItemData(
          product: product,
          quantity: quantity,
          quantityConverted: converted.convertedQuantity,
          boxes: converted.boxes,
        );

        // Add item and mark as NOT selected by default
        final updatedItems = [...currentState.items, newItem];
        final updatedSelection = Map<String, bool>.from(currentState.selectedItems);
        updatedSelection[product.productId] = false;

        final newState = currentState.copyWith(
          items: updatedItems,
          selectedItems: updatedSelection,
          isLoading: false,
        );

        state = _recalculateTotal(newState);
      }
    } catch (e) {
      // Show error but keep current state
      state = currentState.copyWith(
        isLoading: false,
        errorMessage: 'Failed to add item: ${e.toString()}',
      );
    }
  }

  /// Calculate conversion based on business rules
  ///
  /// Business Rules:
  /// 1. Số viên (boxes/tiles) = Số lượng x Conversion Factor - Round UP
  ///    Example: 6.43 → 7 viên
  /// 2. Số m² (converted) = Số viên / Conversion Factor - Round to 2 decimals
  /// 3. Tạm tính = Số m² (converted) x Đơn giá
  ({double convertedQuantity, int boxes}) _calculateConversion(
    double quantity,
    double? conversionFactor,
  ) {
    // Use product's conversion factor, default to 1.0 if not set
    final factor = conversionFactor ?? 1.0;

    // Step 1: Calculate number of tiles/boxes needed (ROUND UP)
    final exactBoxes = quantity * factor;
    final boxes = exactBoxes.ceil(); // Round up: 6.43 → 7

    // Step 2: Calculate converted m² from rounded boxes (2 decimal places)
    final convertedM2 = boxes / factor;
    final converted = (convertedM2 * 100).roundToDouble() / 100; // Round to 2 decimals

    return (convertedQuantity: converted, boxes: boxes);
  }

  /// Remove product from cart (API + Local)
  Future<void> removeFromCart(String productId) async {
    final repository = await ref.read(cartRepositoryProvider.future);
    final currentState = state;

    // Find the item to get its ERPNext item code
    final item = currentState.items.firstWhere(
      (item) => item.product.productId == productId,
      orElse: () => throw Exception('Item not found'),
    );

    // Set loading state
    state = currentState.copyWith(isLoading: true, errorMessage: null);

    try {
      // Remove from API
      await repository.removeFromCart(
        itemIds: [item.product.erpnextItemCode ?? productId],
      );

      // Remove from local state
      final updatedSelection = Map<String, bool>.from(currentState.selectedItems)
        ..remove(productId);

      final newState = currentState.copyWith(
        items: currentState.items
            .where((item) => item.product.productId != productId)
            .toList(),
        selectedItems: updatedSelection,
        isLoading: false,
      );

      state = _recalculateTotal(newState);
    } catch (e) {
      state = currentState.copyWith(
        isLoading: false,
        errorMessage: 'Failed to remove item: ${e.toString()}',
      );
    }
  }

  /// Update item quantity immediately (local only, no API call)
  ///
  /// Used for instant UI updates. Actual API sync happens after debounce.
  void updateQuantityLocal(String productId, double newQuantity) {
    if (newQuantity <= 0) {
      // For zero quantity, remove immediately
      removeFromCart(productId);
      return;
    }

    final currentState = state;

    // Find the item
    final itemIndex = currentState.items.indexWhere(
      (item) => item.product.productId == productId,
    );

    if (itemIndex == -1) return;

    final item = currentState.items[itemIndex];

    // Update local state immediately
    final converted = _calculateConversion(
      newQuantity,
      item.product.conversionOfSm,
    );

    final updatedItems = List<CartItemData>.from(currentState.items);
    updatedItems[itemIndex] = item.copyWith(
      quantity: newQuantity,
      quantityConverted: converted.convertedQuantity,
      boxes: converted.boxes,
    );

    final newState = currentState.copyWith(items: updatedItems);
    state = _recalculateTotal(newState);

    // Track pending update for API sync
    _pendingQuantityUpdates[productId] = newQuantity;

    // Schedule debounced API sync
    _scheduleDebouncedSync();
  }

  /// Schedule debounced sync to API (3 seconds after last change)
  void _scheduleDebouncedSync() {
    // Cancel existing timer
    _debounceTimer?.cancel();

    // Start new timer (3 seconds debounce)
    _debounceTimer = Timer(const Duration(seconds: 3), () {
      _syncPendingQuantityUpdates();
    });
  }

  /// Sync all pending quantity updates to API
  Future<void> _syncPendingQuantityUpdates() async {
    if (_pendingQuantityUpdates.isEmpty) return;

    final repository = await ref.read(cartRepositoryProvider.future);
    final currentState = state;

    // Create a copy of pending updates
    final updates = Map<String, double>.from(_pendingQuantityUpdates);
    _pendingQuantityUpdates.clear();

    // Sync each update to API
    for (final entry in updates.entries) {
      final productId = entry.key;
      final quantity = entry.value;

      // Find the item
      final item = currentState.items.firstWhere(
        (item) => item.product.productId == productId,
        orElse: () => throw Exception('Item not found'),
      );

      try {
        // Update via API (no loading state, happens in background)
        await repository.updateQuantity(
          itemId: item.product.erpnextItemCode ?? productId,
          quantity: quantity,
          price: item.product.basePrice,
          conversionFactor: item.product.conversionOfSm,
        );
      } catch (e) {
        // Silent fail - keep local state, user can retry later
        // TODO: Add to offline queue for retry
        // ignore: avoid_print
        print('[Cart] Failed to sync quantity for $productId: $e');
      }
    }
  }

  /// Update item quantity with API sync (immediate, no debounce)
  ///
  /// Use this for direct updates (not from increment/decrement buttons).
  /// For increment/decrement, use updateQuantityLocal instead.
  Future<void> updateQuantity(String productId, double newQuantity) async {
    if (newQuantity <= 0) {
      await removeFromCart(productId);
      return;
    }

    final repository = await ref.read(cartRepositoryProvider.future);
    final currentState = state;

    // Find the item
    final item = currentState.items.firstWhere(
      (item) => item.product.productId == productId,
      orElse: () => throw Exception('Item not found'),
    );

    // Set loading state
    state = currentState.copyWith(isLoading: true, errorMessage: null);

    try {
      // Update via API
      await repository.updateQuantity(
        itemId: item.product.erpnextItemCode ?? productId,
        quantity: newQuantity,
        price: item.product.basePrice,
        conversionFactor: item.product.conversionOfSm,
      );

      // Update local state
      final converted = _calculateConversion(
        newQuantity,
        item.product.conversionOfSm,
      );

      final updatedItems = currentState.items.map((currentItem) {
        if (currentItem.product.productId == productId) {
          return currentItem.copyWith(
            quantity: newQuantity,
            quantityConverted: converted.convertedQuantity,
            boxes: converted.boxes,
          );
        }
        return currentItem;
      }).toList();

      final newState = currentState.copyWith(
        items: updatedItems,
        isLoading: false,
      );

      state = _recalculateTotal(newState);
    } catch (e) {
      state = currentState.copyWith(
        isLoading: false,
        errorMessage: 'Failed to update quantity: ${e.toString()}',
      );
    }
  }

  /// Increment quantity (with debounce)
  ///
  /// Updates UI immediately, syncs to API after 3s of no changes.
  void incrementQuantity(String productId) {
    final currentState = state;
    final item = currentState.items.firstWhere(
      (item) => item.product.productId == productId,
    );
    updateQuantityLocal(productId, item.quantity + 1);
  }

  /// Decrement quantity (minimum 1, with debounce)
  ///
  /// Updates UI immediately, syncs to API after 3s of no changes.
  void decrementQuantity(String productId) {
    final currentState = state;
    final item = currentState.items.firstWhere(
      (item) => item.product.productId == productId,
    );
    // Keep minimum quantity at 1, don't go to 0
    if (item.quantity > 1) {
      updateQuantityLocal(productId, item.quantity - 1);
    }
  }

  /// Force sync all pending quantity updates immediately
  ///
  /// Useful when user navigates away or closes cart.
  Future<void> forceSyncPendingUpdates() async {
    _debounceTimer?.cancel();
    await _syncPendingQuantityUpdates();
  }

  /// Clear entire cart (API + Local)
  Future<void> clearCart() async {
    final repository = await ref.read(cartRepositoryProvider.future);
    final currentState = state;

    state = currentState.copyWith(isLoading: true, errorMessage: null);

    try {
      await repository.clearCart();
      state = CartState.initial().copyWith(
        memberTier: currentState.memberTier,
        memberDiscountPercent: currentState.memberDiscountPercent,
      );
    } catch (e) {
      state = currentState.copyWith(
        isLoading: false,
        errorMessage: 'Failed to clear cart: ${e.toString()}',
      );
    }
  }

  /// Toggle item selection (Local only)
  void toggleSelection(String productId) {
    final currentState = state;
    final updatedSelection = Map<String, bool>.from(currentState.selectedItems);
    updatedSelection[productId] = !(updatedSelection[productId] ?? false);
    state = _recalculateTotal(currentState.copyWith(selectedItems: updatedSelection));
  }

  /// Toggle select all (Local only)
  void toggleSelectAll() {
    final currentState = state;
    final allSelected = currentState.isAllSelected;
    final updatedSelection = <String, bool>{};
    for (final item in currentState.items) {
      updatedSelection[item.product.productId] = !allSelected;
    }
    state = _recalculateTotal(currentState.copyWith(selectedItems: updatedSelection));
  }

  /// Delete selected items (API + Local)
  Future<void> deleteSelected() async {
    final repository = await ref.read(cartRepositoryProvider.future);
    final currentState = state;

    final selectedIds = currentState.selectedItems.entries
        .where((entry) => entry.value)
        .map((entry) => entry.key)
        .toSet();

    if (selectedIds.isEmpty) return;

    state = currentState.copyWith(isLoading: true, errorMessage: null);

    try {
      // Get ERPNext item codes for selected items
      final itemCodesToRemove = currentState.items
          .where((item) => selectedIds.contains(item.product.productId))
          .map((item) => item.product.erpnextItemCode ?? item.product.productId)
          .toList();

      // Remove from API
      await repository.removeFromCart(itemIds: itemCodesToRemove);

      // Remove from local state
      final remainingItems = currentState.items
          .where((item) => !selectedIds.contains(item.product.productId))
          .toList();

      final updatedSelection = Map<String, bool>.from(currentState.selectedItems);
      for (final id in selectedIds) {
        updatedSelection.remove(id);
      }

      final newState = currentState.copyWith(
        items: remainingItems,
        selectedItems: updatedSelection,
        isLoading: false,
      );

      state = _recalculateTotal(newState);
    } catch (e) {
      state = currentState.copyWith(
        isLoading: false,
        errorMessage: 'Failed to delete items: ${e.toString()}',
      );
    }
  }

  /// Select warehouse (Local only)
  void selectWarehouse(String warehouse) {
    final currentState = state;
    state = currentState.copyWith(selectedWarehouse: warehouse);
  }

  /// Apply discount code (Local only for now)
  void applyDiscountCode(String code) {
    // TODO: Validate with backend API
    final currentState = state;
    if (code.isNotEmpty) {
      final newState = currentState.copyWith(
        discountCode: code,
        discountCodeApplied: true,
      );
      state = _recalculateTotal(newState);
    }
  }

  /// Remove discount code (Local only)
  void removeDiscountCode() {
    final currentState = state;
    final newState = currentState.copyWith(
      discountCode: null,
      discountCodeApplied: false,
    );
    state = _recalculateTotal(newState);
  }

  /// Recalculate cart totals (Local calculation)
  CartState _recalculateTotal(CartState currentState) {
    // Calculate subtotal using CONVERTED quantities for selected items only
    double subtotal = 0.0;
    for (final item in currentState.items) {
      if (currentState.selectedItems[item.product.productId] == true) {
        subtotal += item.lineTotal; // Uses quantityConverted
      }
    }

    // Calculate member tier discount
    final memberDiscount = subtotal * (currentState.memberDiscountPercent / 100);

    // Calculate shipping (free for now)
    const shippingFee = 0.0;

    // Calculate total
    final total = subtotal - memberDiscount + shippingFee;

    return currentState.copyWith(
      subtotal: subtotal,
      memberDiscount: memberDiscount,
      shippingFee: shippingFee,
      total: total,
    );
  }

  /// Get total quantity of all items
  double get totalQuantity {
    return state.items.fold<double>(0.0, (sum, item) => sum + item.quantity);
  }
}

/// Cart item count provider
/// keepAlive: true to persist with cart provider
@Riverpod(keepAlive: true)
int cartItemCount(Ref ref) {
  final cartState = ref.watch(cartProvider);
  return cartState.items.length;
}

/// Cart total provider
/// keepAlive: true to persist with cart provider
@Riverpod(keepAlive: true)
double cartTotal(Ref ref) {
  final cartState = ref.watch(cartProvider);
  return cartState.total;
}
