package com.aistudio.bonjuteri.jwlry.data import android.content.Context import android.util.Log import com.aistudio.bonjuteri.jwlry.model.Product import com.google.firebase.Timestamp import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.tasks.await class ProductRepository( private val firestore: FirebaseFirestore ) { companion object { private const val TAG = "ProductRepository" const val PRODUCTS_COLLECTION = "products" @Volatile private var INSTANCE: ProductRepository? = null fun getInstance(context: Context): ProductRepository { return INSTANCE ?: synchronized(this) { INSTANCE ?: ProductRepository( FirestoreHelper.getFirestore(context) ).also { INSTANCE = it } } } } /** * Real-time flow of all products, ordered by createdAt descending */ fun observeProducts(onlyActive: Boolean = true): Flow> = callbackFlow { var query: Query = firestore.collection(PRODUCTS_COLLECTION) if (onlyActive) { query = query.whereEqualTo("isActive", true) } val listener = query.addSnapshotListener { snapshot, error -> if (error != null) { FirestoreHelper.handleFirestoreError(error, "observeProducts", PRODUCTS_COLLECTION) trySend(emptyList()) return@addSnapshotListener } val products = snapshot?.documents?.mapNotNull { doc -> try { doc.toObject(Product::class.java) } catch (e: Exception) { Log.w(TAG, "Ürün deserialize hatası: ${e.message} for doc ${doc.id}") null } } ?: emptyList() trySend(products) } awaitClose { listener.remove() } } /** * Real-time flow of single product */ fun observeProduct(productId: String): Flow = callbackFlow { val docRef = firestore.collection(PRODUCTS_COLLECTION).document(productId) val listener = docRef.addSnapshotListener { snapshot, error -> if (error != null) { FirestoreHelper.handleFirestoreError(error, "observeProduct", docRef.path) trySend(null) return@addSnapshotListener } val product = snapshot?.toObject(Product::class.java) trySend(product) } awaitClose { listener.remove() } } suspend fun getProduct(productId: String): Product? { val doc = firestore.collection(PRODUCTS_COLLECTION).document(productId).get().await() return doc.toObject(Product::class.java) } /** * Strict validation and product creation in Firestore */ suspend fun addProduct( title: String, description: String, sku: String, category: String, price: Double, oldPrice: Double?, stock: Long, imageUrl: String, isActive: Boolean, isNew: Boolean, isBestSeller: Boolean, isFeatured: Boolean ): Result { val trimmedTitle = title.trim() val trimmedCategory = category.trim() val trimmedSku = sku.trim() val trimmedImageUrl = imageUrl.trim() // Validation rules requested in specification if (trimmedTitle.isBlank()) { return Result.failure(IllegalArgumentException("Ürün adı boş olamaz.")) } if (price <= 0.0) { return Result.failure(IllegalArgumentException("Satış fiyatı sıfırdan büyük olmalıdır.")) } if (oldPrice != null && oldPrice < price) { return Result.failure(IllegalArgumentException("Eski fiyat satış fiyatından düşük olamaz.")) } if (stock < 0L) { return Result.failure(IllegalArgumentException("Stok miktarı negatif olamaz.")) } if (trimmedCategory.isBlank()) { return Result.failure(IllegalArgumentException("Lütfen geçerli bir kategori seçiniz.")) } if (trimmedImageUrl.isBlank()) { return Result.failure(IllegalArgumentException("Lütfen ürün görseli ekleyiniz veya seçiniz.")) } return try { val docRef = firestore.collection(PRODUCTS_COLLECTION).document() val newProduct = Product( id = docRef.id, title = trimmedTitle, description = description.trim(), sku = if (trimmedSku.isNotBlank()) trimmedSku else "BNJ-${System.currentTimeMillis() % 100000}", category = trimmedCategory, price = price, oldPrice = oldPrice, stock = stock, imageUrl = trimmedImageUrl, isActive = isActive, isNew = isNew, isBestSeller = isBestSeller, isFeatured = isFeatured, createdAt = Timestamp.now(), updatedAt = Timestamp.now() ) docRef.set(newProduct).await() Log.d(TAG, "Ürün başarıyla Firestore'a eklendi: ${newProduct.id}") Result.success(newProduct) } catch (e: Throwable) { val friendlyMsg = FirestoreHelper.handleFirestoreError(e, "addProduct", PRODUCTS_COLLECTION) Log.e(TAG, "Ürün ekleme hatası: $friendlyMsg", e) Result.failure(Exception(friendlyMsg)) } } /** * Update product */ suspend fun updateProduct(product: Product): Result { if (product.id.isBlank()) { return Result.failure(IllegalArgumentException("Güncellenecek ürün kimliği bulunamadı.")) } if (product.title.isBlank()) { return Result.failure(IllegalArgumentException("Ürün adı boş olamaz.")) } if (product.price <= 0.0) { return Result.failure(IllegalArgumentException("Satış fiyatı sıfırdan büyük olmalıdır.")) } if (product.stock < 0L) { return Result.failure(IllegalArgumentException("Stok miktarı negatif olamaz.")) } if (product.category.isBlank()) { return Result.failure(IllegalArgumentException("Kategori seçilmelidir.")) } return try { val docRef = firestore.collection(PRODUCTS_COLLECTION).document(product.id) val updated = product.copy(updatedAt = Timestamp.now()) docRef.set(updated).await() Result.success(updated) } catch (e: Throwable) { val friendlyMsg = FirestoreHelper.handleFirestoreError(e, "updateProduct", product.id) Result.failure(Exception(friendlyMsg)) } } /** * Delete product */ suspend fun deleteProduct(productId: String): Result { return try { firestore.collection(PRODUCTS_COLLECTION).document(productId).delete().await() Result.success(Unit) } catch (e: Throwable) { val friendlyMsg = FirestoreHelper.handleFirestoreError(e, "deleteProduct", productId) Result.failure(Exception(friendlyMsg)) } } }