import 'dart:developer';

import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';

import '../../../domain/product/product.dart';
import '../datasources/local_data_provider.dart';
import '../datasources/remote_data_provider.dart';
import '../product_dtos.dart';

@Injectable(as: IProductRepository)
class ProductRepository implements IProductRepository {
  final ProductRemoteDataProvider _remoteDataProvider;
  final ProductLocalDataProvider _localDataProvider;

  final _logName = 'ProductRepository';

  ProductRepository(this._remoteDataProvider, this._localDataProvider);

  @override
  Future<Either<ProductFailure, Product>> getProductById(String id) async {
    try {
      log('🔍 Getting product by ID from local: $id', name: _logName);

      final product = await _localDataProvider.getProductById(id);

      if (product.hasData) {
        log('❌ Product not found: $id', name: _logName);
        return Left(
          ProductFailure.dynamicErrorMessage(
            'Produk dengan ID $id tidak ditemukan',
          ),
        );
      }

      final productDomain = product.data!.toDomain();

      log('✅ Product loaded: ${productDomain.name}', name: _logName);
      return Right(productDomain);
    } catch (e, s) {
      log(
        '❌ Error getting product by ID',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(ProductFailure.localStorageError(e.toString()));
    }
  }

  @override
  Future<Either<ProductFailure, ListProduct>> getProducts({
    int page = 1,
    int limit = 10,
    String? categoryId,
    String? search,
    bool forceRefresh = false,
  }) async {
    try {
      log(
        '📦 Fetching products from local DB - page: $page, categoryId: $categoryId, search: $search',
        name: _logName,
      );

      // 🧹 Bersihkan cache kedaluwarsa
      _localDataProvider.clearExpiredCache();

      // ⚡ Ambil data dari cache lokal
      final cachedProducts = await _localDataProvider.getCachedProducts(
        page: page,
        limit: limit,
        categoryId: categoryId,
        search: search,
      );

      if (cachedProducts.hasError) {
        return left(cachedProducts.error!);
      }

      // 📊 Hitung total item (untuk pagination)
      final totalCount = await _localDataProvider.getTotalCount(
        categoryId: categoryId,
        search: search,
      );

      // 🧱 Bangun entity domain ListProduct
      final result = ListProduct(
        products: cachedProducts.data!.map((p) => p.toDomain()).toList(),
        totalCount: totalCount,
        page: page,
        limit: limit,
        totalPages: totalCount > 0 ? (totalCount / limit).ceil() : 0,
      );

      log(
        '✅ Returned ${cachedProducts.data!.length} local products ($totalCount total)',
        name: _logName,
      );

      return Right(result);
    } catch (e, s) {
      log(
        '❌ Error getting local products',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(ProductFailure.localStorageError(e.toString()));
    }
  }

  @override
  Future<Either<ProductFailure, ListProduct>> refreshProducts({
    String? categoryId,
    String? search,
  }) async {
    try {
      log('🔄 Refreshing local products...', name: _logName);

      // Bersihkan cache agar hasil baru diambil dari database
      _localDataProvider.clearCache();

      // Ambil ulang data produk dari lokal database
      final cachedProducts = await _localDataProvider.getCachedProducts(
        page: 1,
        limit: 10,
        categoryId: categoryId,
        search: search,
      );

      if (cachedProducts.hasError) {
        return left(cachedProducts.error!);
      }

      final products = cachedProducts.data!.map((p) => p.toDomain()).toList();

      final totalCount = await _localDataProvider.getTotalCount(
        categoryId: categoryId,
        search: search,
      );

      // 🧱 Bangun entity domain ListProduct
      final result = ListProduct(
        products: products,
        totalCount: totalCount,
        page: 1,
        limit: 10,
        totalPages: totalCount > 0 ? (totalCount / 10).ceil() : 0,
      );

      log(
        '✅ Refreshed ${cachedProducts.data!.length} local products',
        name: _logName,
      );

      return Right(result);
    } catch (e, s) {
      log(
        '❌ Error refreshing local products',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(ProductFailure.localStorageError(e.toString()));
    }
  }

  @override
  Future<Either<ProductFailure, List<Product>>> searchProductsOptimized(
    String query,
  ) async {
    try {
      log('🔍 Local optimized search for: "$query"', name: _logName);

      // 🔎 Cari dari local database
      final results = await _localDataProvider.searchProductsOptimized(query);

      if (results.hasError) {
        return left(results.error!);
      }

      // ✅ Mapping ke domain entity (kalau hasilnya masih berupa DTO)
      final products = results.data!.map((p) => p.toDomain()).toList();

      log(
        '✅ Local search completed: ${products.length} results',
        name: _logName,
      );

      return Right(products);
    } catch (e, s) {
      log('❌ Error in local search', name: _logName, error: e, stackTrace: s);
      return Left(ProductFailure.localStorageError(e.toString()));
    }
  }

  @override
  Future<Either<ProductFailure, String>> syncAllProducts() async {
    try {
      log('🔄 Starting manual sync of all products...', name: _logName);

      int page = 1;
      const limit = 50;
      bool hasMore = true;
      int totalSynced = 0;

      // Clear local DB before fresh sync
      await _localDataProvider.clearAllProducts();

      while (hasMore) {
        log('📄 Syncing page $page...', name: _logName);

        // NOTE: _remoteDatasource.getProducts() returns DC<..., ProductResponseModel>
        final remoteResult = await _remoteDataProvider.fetchProducts(
          page: page,
          limit: limit,
        );

        // Handle DC result manually (no fold)
        if (!remoteResult.hasData) {
          // remote returned an error/failure
          final remoteFailure = remoteResult.error;
          log('❌ Sync failed at page $page: $remoteFailure', name: _logName);
          return Left(
            ProductFailure.dynamicErrorMessage(remoteFailure.toString()),
          );
        }

        final response = remoteResult.data!;
        final products = response.products;

        if (products.isNotEmpty) {
          // Save page to local DB
          await _localDataProvider.saveProductsBatch(
            products,
            clearFirst: false, // don't clear on subsequent pages
          );

          totalSynced += products.length;

          // Determine if more pages exist
          hasMore = page < (response.totalPages);
          page++;

          log(
            '📦 Page ${page - 1} synced: ${products.length} products',
            name: _logName,
          );
        } else {
          hasMore = false;
        }
      }

      final message = 'Berhasil sinkronisasi $totalSynced produk';
      log('✅ $message', name: _logName);
      return Right(message);
    } catch (e, s) {
      log(
        '❌ Gagal sinkronisasi produk',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(ProductFailure.localStorageError(e.toString()));
    }
  }

  @override
  void clearCache() {
    log('🧹 Clearing local cache', name: _logName);
    _localDataProvider.clearCache();
  }

  @override
  Future<Either<ProductFailure, Map<String, dynamic>>>
  getDatabaseStats() async {
    try {
      log('📊 Getting local database stats...', name: _logName);

      final stats = await _localDataProvider.getDatabaseStats();

      log('✅ Database stats loaded successfully: $stats', name: _logName);

      return Right(stats.data!);
    } catch (e, s) {
      log(
        '❌ Error getting database stats',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(ProductFailure.localStorageError(e.toString()));
    }
  }

  @override
  Future<bool> hasLocalProducts() async {
    final hasProducts = await _localDataProvider.hasProducts();
    log('📊 Has local products: $hasProducts', name: _logName);
    return hasProducts;
  }

  @override
  Future<bool> isLocalDatabaseReady() async {
    try {
      final stats = await _localDataProvider.getDatabaseStats();
      final productCount = stats.data!['total_products'] ?? 0;
      final isReady = productCount > 0;
      log(
        '🔍 Local database ready: $isReady ($productCount products)',
        name: _logName,
      );
      return isReady;
    } catch (e) {
      log('❌ Error checking database readiness: $e', name: _logName);
      return false;
    }
  }

  @override
  Future<void> clearAllProducts() async {
    try {
      log('🗑️ Clearing all products from repository...', name: _logName);
      await _localDataProvider.clearAllProducts();
      clearCache();
      log('✅ All products cleared successfully', name: _logName);
    } catch (e) {
      log('❌ Error clearing all products: $e', name: _logName);
      rethrow;
    }
  }

  @override
  Future<Either<ProductFailure, Unit>> saveProductsBatch(
    List<Product> products,
  ) async {
    try {
      final productDtos = products.map(ProductDto.fromDomain).toList();

      // Simpan batch ke local DB
      await _localDataProvider.saveProductsBatch(productDtos);

      log('💾 Saved ${products.length} products to local DB', name: _logName);
      return right(unit);
    } catch (e, stack) {
      log('❌ Failed to save products batch: $e\n$stack', name: _logName);
      return left(ProductFailure.dynamicErrorMessage(e.toString()));
    }
  }

  @override
  Future<Either<ProductFailure, ListProduct>> getRemoteProducts({
    int page = 1,
    int limit = 10,
    String? categoryId,
    String? search,
  }) async {
    try {
      final result = await _remoteDataProvider.fetchProducts(
        page: page,
        limit: limit,
        categoryId: categoryId,
        search: search,
      );

      if (result.hasError) {
        return left(result.error!);
      }

      final products = result.data!.toDomain();

      return right(products);
    } catch (e, s) {
      log('getProducts', name: _logName, error: e, stackTrace: s);
      return left(const ProductFailure.unexpectedError());
    }
  }
}
