import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:enaklo_pos/core/database/database_handler.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart' as p; class ProductLocalDatasource { static ProductLocalDatasource? _instance; ProductLocalDatasource._internal(); static ProductLocalDatasource get instance { _instance ??= ProductLocalDatasource._internal(); return _instance!; } Future get _db async => await DatabaseHelper.instance.database; // ======================================== // CACHING SYSTEM // ======================================== final Map> _queryCache = {}; final Duration _cacheExpiry = Duration(minutes: 5); final Map _cacheTimestamps = {}; // ======================================== // ENHANCED BATCH SAVE // ======================================== Future saveProductsBatch(List products, {bool clearFirst = false}) async { final db = await _db; try { await db.transaction((txn) async { if (clearFirst) { log('๐Ÿ—‘๏ธ Clearing existing products...'); await txn.delete('product_variants'); await txn.delete('products'); } log('๐Ÿ’พ Batch saving ${products.length} products...'); // โœ… BATCH INSERT PRODUCTS - Much faster than individual inserts final batch = txn.batch(); for (final product in products) { batch.insert( 'products', _productToMap(product), conflictAlgorithm: ConflictAlgorithm.replace, ); } await batch.commit(noResult: true); // โœ… BATCH INSERT VARIANTS final variantBatch = txn.batch(); for (final product in products) { if (product.variants?.isNotEmpty == true) { // Delete existing variants in batch variantBatch.delete( 'product_variants', where: 'product_id = ?', whereArgs: [product.id], ); // Insert new variants for (final variant in product.variants!) { variantBatch.insert( 'product_variants', _variantToMap(variant), conflictAlgorithm: ConflictAlgorithm.replace, ); } } } await variantBatch.commit(noResult: true); }); // Clear cache after update clearCache(); log('โœ… Successfully batch saved ${products.length} products'); } catch (e) { log('โŒ Error batch saving products: $e'); rethrow; } } // ======================================== // CACHED QUERY - HIGH PERFORMANCE // ======================================== Future> getCachedProducts({ int page = 1, int limit = 10, String? categoryId, String? search, }) async { final cacheKey = _generateCacheKey(page, limit, categoryId, search); final now = DateTime.now(); // โœ… CHECK CACHE FIRST if (_queryCache.containsKey(cacheKey) && _cacheTimestamps.containsKey(cacheKey)) { final cacheTime = _cacheTimestamps[cacheKey]!; if (now.difference(cacheTime) < _cacheExpiry) { log('๐Ÿš€ Cache HIT: $cacheKey (${_queryCache[cacheKey]!.length} products)'); return _queryCache[cacheKey]!; // Return from cache - SUPER FAST } } log('๐Ÿ“€ Cache MISS: $cacheKey, querying database...'); // Cache miss, query database final products = await getProducts( page: page, limit: limit, categoryId: categoryId, search: search, ); // โœ… STORE IN CACHE for next time _queryCache[cacheKey] = products; _cacheTimestamps[cacheKey] = now; log('๐Ÿ’พ Cached ${products.length} products for key: $cacheKey'); return products; } // ======================================== // REGULAR GET PRODUCTS (No Cache) // ======================================== Future> getProducts({ int page = 1, int limit = 10, String? categoryId, String? search, }) async { final db = await _db; try { String query = 'SELECT * FROM products WHERE 1=1'; List whereArgs = []; if (categoryId != null && categoryId.isNotEmpty) { query += ' AND category_id = ?'; whereArgs.add(categoryId); } if (search != null && search.isNotEmpty) { query += ' AND (name LIKE ? OR sku LIKE ? OR description LIKE ?)'; whereArgs.add('%$search%'); whereArgs.add('%$search%'); whereArgs.add('%$search%'); } query += ' ORDER BY created_at DESC'; if (limit > 0) { query += ' LIMIT ?'; whereArgs.add(limit); if (page > 1) { query += ' OFFSET ?'; whereArgs.add((page - 1) * limit); } } final List> maps = await db.rawQuery(query, whereArgs); List products = []; for (final map in maps) { final variants = await _getProductVariants(db, map['id']); final product = _mapToProduct(map, variants); products.add(product); } log('๐Ÿ“Š Retrieved ${products.length} products from database'); return products; } catch (e) { log('โŒ Error getting products: $e'); return []; } } // ======================================== // OPTIMIZED SEARCH with RANKING // ======================================== Future> searchProductsOptimized(String query) async { final db = await _db; try { log('๐Ÿ” Optimized search for: "$query"'); // โœ… Smart query with prioritization final List> maps = await db.rawQuery(''' SELECT * FROM products WHERE name LIKE ? OR sku LIKE ? OR description LIKE ? ORDER BY CASE WHEN name LIKE ? THEN 1 -- Highest priority: name match WHEN sku LIKE ? THEN 2 -- Second priority: SKU match ELSE 3 -- Lowest priority: description END, name ASC LIMIT 50 ''', [ '%$query%', '%$query%', '%$query%', '$query%', '$query%' // Prioritize results that start with query ]); List products = []; for (final map in maps) { final variants = await _getProductVariants(db, map['id']); products.add(_mapToProduct(map, variants)); } log('๐ŸŽฏ Optimized search found ${products.length} results'); return products; } catch (e) { log('โŒ Error in optimized search: $e'); return []; } } // ======================================== // DATABASE ANALYTICS & MONITORING // ======================================== Future> getDatabaseStats() async { final db = await _db; try { final productCount = Sqflite.firstIntValue( await db.rawQuery('SELECT COUNT(*) FROM products')) ?? 0; final variantCount = Sqflite.firstIntValue( await db.rawQuery('SELECT COUNT(*) FROM product_variants')) ?? 0; final categoryCount = Sqflite.firstIntValue(await db.rawQuery( 'SELECT COUNT(DISTINCT category_id) FROM products WHERE category_id IS NOT NULL')) ?? 0; final dbSize = await _getDatabaseSize(); final stats = { 'total_products': productCount, 'total_variants': variantCount, 'total_categories': categoryCount, 'database_size_mb': dbSize, 'cache_entries': _queryCache.length, 'cache_size_mb': _getCacheSize(), }; log('๐Ÿ“Š Database Stats: $stats'); return stats; } catch (e) { log('โŒ Error getting database stats: $e'); return {}; } } Future _getDatabaseSize() async { try { final dbPath = p.join(await getDatabasesPath(), 'pos_database.db'); final file = File(dbPath); if (await file.exists()) { final size = await file.length(); return size / (1024 * 1024); // Convert to MB } } catch (e) { log('Error getting database size: $e'); } return 0.0; } double _getCacheSize() { double totalSize = 0; _queryCache.forEach((key, products) { totalSize += products.length * 0.001; // Rough estimate in MB }); return totalSize; } // ======================================== // CACHE MANAGEMENT // ======================================== String _generateCacheKey( int page, int limit, String? categoryId, String? search) { return 'products_${page}_${limit}_${categoryId ?? 'null'}_${search ?? 'null'}'; } void clearCache() { final count = _queryCache.length; _queryCache.clear(); _cacheTimestamps.clear(); log('๐Ÿงน Cache cleared: $count entries removed'); } void clearExpiredCache() { final now = DateTime.now(); final expiredKeys = []; _cacheTimestamps.forEach((key, timestamp) { if (now.difference(timestamp) > _cacheExpiry) { expiredKeys.add(key); } }); for (final key in expiredKeys) { _queryCache.remove(key); _cacheTimestamps.remove(key); } if (expiredKeys.isNotEmpty) { log('โฐ Expired cache cleared: ${expiredKeys.length} entries'); } } // ======================================== // OTHER METHODS (Same as basic but with enhanced logging) // ======================================== Future getProductById(String id) async { final db = await _db; try { final List> maps = await db.query( 'products', where: 'id = ?', whereArgs: [id], ); if (maps.isEmpty) { log('โŒ Product not found: $id'); return null; } final variants = await _getProductVariants(db, id); final product = _mapToProduct(maps.first, variants); log('โœ… Product found: ${product.name}'); return product; } catch (e) { log('โŒ Error getting product by ID: $e'); return null; } } Future getTotalCount({String? categoryId, String? search}) async { final db = await _db; try { String query = 'SELECT COUNT(*) FROM products WHERE 1=1'; List whereArgs = []; if (categoryId != null && categoryId.isNotEmpty) { query += ' AND category_id = ?'; whereArgs.add(categoryId); } if (search != null && search.isNotEmpty) { query += ' AND (name LIKE ? OR sku LIKE ? OR description LIKE ?)'; whereArgs.add('%$search%'); whereArgs.add('%$search%'); whereArgs.add('%$search%'); } final result = await db.rawQuery(query, whereArgs); final count = Sqflite.firstIntValue(result) ?? 0; log('๐Ÿ“Š Total count: $count (categoryId: $categoryId, search: $search)'); return count; } catch (e) { log('โŒ Error getting total count: $e'); return 0; } } Future hasProducts() async { final count = await getTotalCount(); final hasData = count > 0; log('๐Ÿ” Has products: $hasData ($count products)'); return hasData; } Future clearAllProducts() async { final db = await _db; try { await db.transaction((txn) async { await txn.delete('product_variants'); await txn.delete('products'); }); clearCache(); log('๐Ÿ—‘๏ธ All products cleared from local DB'); } catch (e) { log('โŒ Error clearing products: $e'); rethrow; } } // ======================================== // HELPER METHODS // ======================================== Future> _getProductVariants( Database db, String productId) async { try { final List> maps = await db.query( 'product_variants', where: 'product_id = ?', whereArgs: [productId], orderBy: 'name ASC', ); return maps.map((map) => _mapToVariant(map)).toList(); } catch (e) { log('โŒ Error getting variants for product $productId: $e'); return []; } } Map _productToMap(Product product) { return { 'id': product.id, 'organization_id': product.organizationId, 'category_id': product.categoryId, 'sku': product.sku, 'name': product.name, 'description': product.description, 'price': product.price, 'cost': product.cost, 'business_type': product.businessType, 'image_url': product.imageUrl, 'printer_type': product.printerType, 'metadata': product.metadata != null ? json.encode(product.metadata) : null, 'is_active': product.isActive == true ? 1 : 0, 'created_at': product.createdAt?.toIso8601String(), 'updated_at': product.updatedAt?.toIso8601String(), }; } Map _variantToMap(ProductVariant variant) { return { 'id': variant.id, 'product_id': variant.productId, 'name': variant.name, 'price_modifier': variant.priceModifier, 'cost': variant.cost, 'metadata': variant.metadata != null ? json.encode(variant.metadata) : null, 'created_at': variant.createdAt?.toIso8601String(), 'updated_at': variant.updatedAt?.toIso8601String(), }; } Product _mapToProduct( Map map, List variants) { return Product( id: map['id'], organizationId: map['organization_id'], categoryId: map['category_id'], sku: map['sku'], name: map['name'], description: map['description'], price: map['price'], cost: map['cost'], businessType: map['business_type'], imageUrl: map['image_url'], printerType: map['printer_type'], metadata: map['metadata'] != null ? json.decode(map['metadata']) : null, isActive: map['is_active'] == 1, createdAt: map['created_at'] != null ? DateTime.parse(map['created_at']) : null, updatedAt: map['updated_at'] != null ? DateTime.parse(map['updated_at']) : null, variants: variants, ); } ProductVariant _mapToVariant(Map map) { return ProductVariant( id: map['id'], productId: map['product_id'], name: map['name'], priceModifier: map['price_modifier'], cost: map['cost'], metadata: map['metadata'] != null ? json.decode(map['metadata']) : null, createdAt: map['created_at'] != null ? DateTime.parse(map['created_at']) : null, updatedAt: map['updated_at'] != null ? DateTime.parse(map['updated_at']) : null, ); } }