import 'dart:developer';

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

import '../../../domain/category/category.dart';
import '../category_dtos.dart';
import '../datasources/local_data_provider.dart';
import '../datasources/remote_data_provider.dart';

@Injectable(as: ICategoryRepository)
class CategoryRepository implements ICategoryRepository {
  final CategoryRemoteDataProvider _remoteDataProvider;
  final CategoryLocalDataProvider _localDataProvider;

  final _logName = 'CategoryRepository';

  CategoryRepository(this._remoteDataProvider, this._localDataProvider);

  @override
  Future<Either<CategoryFailure, ListCategory>> getCategories({
    int page = 1,
    int limit = 10,
    bool isActive = true,
    String? search,
    bool forceRemote = false,
  }) async {
    try {
      log(
        '📱 Getting categories - page: $page, isActive: $isActive, search: $search, forceRemote: $forceRemote',
        name: _logName,
      );

      // Clean expired cache
      _localDataProvider.clearExpiredCache();

      // Check if we should try remote first
      if (forceRemote || !await _localDataProvider.hasCategories()) {
        log('🌐 Attempting remote fetch first...', name: _logName);

        final remoteResult = await _getRemoteCategories(
          page: page,
          limit: limit,
          isActive: isActive,
        );

        return await remoteResult.fold(
          (failure) async {
            log('❌ Remote fetch failed: $failure', name: _logName);
            log('📱 Falling back to local data...', name: _logName);
            return await _getLocalCategories(
              page: page,
              limit: limit,
              isActive: isActive,
              search: search,
            );
          },
          (data) async {
            log(
              '✅ Remote fetch successful, syncing to local...',
              name: _logName,
            );

            // Sync remote data to local
            if (data.categories.isNotEmpty) {
              await _syncToLocal(data.categories, clearFirst: page == 1);
            }

            return Right(data);
          },
        );
      } else {
        log('📱 Using local data (cache available)...', name: _logName);
        return await _getLocalCategories(
          page: page,
          limit: limit,
          isActive: isActive,
          search: search,
        );
      }
    } catch (e, s) {
      log('❌ Error in getCategories', name: _logName, error: e, stackTrace: s);
      return Left(
        CategoryFailure.dynamicErrorMessage('Gagal memuat kategori: $e'),
      );
    }
  }

  @override
  Future<Either<CategoryFailure, Category>> getCategoryById(String id) async {
    try {
      log('🔍 Getting category by ID: $id', name: _logName);

      final result = await _localDataProvider.getCategoryById(id);

      if (result.hasData) {
        final category = result.data!.toDomain();
        log('✅ Category found: ${category.name}', name: _logName);
        return Right(category);
      } else {
        log('❌ Category not found or error: ${result.error}', name: _logName);
        return Left(result.error!);
      }
    } catch (e, s) {
      log(
        '❌ Error getting category by ID',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(
        CategoryFailure.localStorageError('Gagal memuat kategori: $e'),
      );
    }
  }

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

      final result = await _localDataProvider.getDatabaseStats();

      if (result.hasData) {
        final stats = result.data!;
        log('📊 Category database stats: $stats', name: _logName);
        return Right(stats);
      } else {
        log('❌ Error getting stats: ${result.error}', name: _logName);
        return Left(result.error!);
      }
    } catch (e, s) {
      log(
        '❌ Error getting database stats',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(
        CategoryFailure.localStorageError(
          'Gagal memuat statistik database: $e',
        ),
      );
    }
  }

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

  @override
  Future<Either<CategoryFailure, ListCategory>> refreshCategories({
    bool isActive = true,
    String? search,
  }) async {
    try {
      log('🔄 Refreshing categories...', name: _logName);

      // Clear cache before refresh
      _localDataProvider.clearCache();

      return await getCategories(
        page: 1,
        limit: 10,
        isActive: isActive,
        search: search,
        forceRemote: true, // Force remote refresh
      );
    } catch (e, s) {
      log(
        '❌ Error refreshing categories',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(
        CategoryFailure.localStorageError('Gagal memperbarui kategori: $e'),
      );
    }
  }

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

      int page = 1;
      const limit = 50; // Higher limit for bulk sync
      bool hasMore = true;
      int totalSynced = 0;

      // Clear local data first for fresh sync
      await _localDataProvider.clearAllCategories();

      while (hasMore) {
        log('📄 Syncing page $page...');

        final result = await _remoteDataProvider.fetchCategories(
          page: page,
          limit: limit,
          // isActive: true,
        );

        // If fetchCategories returns DC directly, handle it directly
        final data = result.data!.toDomain();

        if (data.categories.isNotEmpty) {
          await _localDataProvider.saveCategoriesBatch(
            data.categories
                .map((category) => CategoryDto.fromDomain(category))
                .toList(),
            clearFirst: false, // Don't clear on subsequent pages
          );
          totalSynced += data.categories.length;

          // Check if we have more pages
          hasMore = page < data.totalPages;
          page++;

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

      final message = 'Berhasil sinkronisasi $totalSynced kategori';
      log('✅ $message');
      return Right(message);
    } catch (e) {
      final error = 'Gagal sinkronisasi kategori: $e';
      log('❌ $error');
      return Left(CategoryFailure.localStorageError(error));
    }
  }

  Future<Either<CategoryFailure, ListCategory>> _getRemoteCategories({
    int page = 1,
    int limit = 10,
    bool isActive = true,
  }) async {
    try {
      log('🌐 Fetching categories from remote...', name: _logName);

      final result = await _remoteDataProvider.fetchCategories(
        page: page,
        limit: limit,
      );

      // Convert DC to Either and DTO to Domain
      if (result.hasData) {
        final categories = result.data!.toDomain();
        return Right(categories);
      } else {
        return Left(result.error!);
      }
    } catch (e, s) {
      log('❌ Remote fetch error', name: _logName, error: e, stackTrace: s);
      return Left(
        CategoryFailure.dynamicErrorMessage(
          'Gagal mengambil data dari server: $e',
        ),
      );
    }
  }

  Future<Either<CategoryFailure, ListCategory>> _getLocalCategories({
    int page = 1,
    int limit = 10,
    bool isActive = true,
    String? search,
  }) async {
    try {
      log('💾 Fetching categories from local...', name: _logName);

      final result = await _localDataProvider.getCachedCategories(
        page: page,
        limit: limit,
        isActive: isActive,
        search: search,
      );

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

      // Convert DC to Either and DTO to Domain
      if (result.hasData) {
        final categories = result.data!.map((dto) => dto.toDomain()).toList();
        final categoryData = ListCategory(
          categories: categories,
          totalCount: totalCount,
          page: page,
          limit: limit,
          totalPages: totalCount > 0 ? (totalCount / limit).ceil() : 0,
        );
        log('✅ Returned ${categories.length} local categories', name: _logName);
        return Right(categoryData);
      } else {
        log(
          '❌ Error getting local categories: ${result.error}',
          name: _logName,
        );
        return Left(result.error!);
      }
    } catch (e, s) {
      log(
        '❌ Error getting local categories',
        name: _logName,
        error: e,
        stackTrace: s,
      );
      return Left(
        CategoryFailure.localStorageError(
          'Gagal memuat kategori dari database lokal: $e',
        ),
      );
    }
  }

  Future<void> _syncToLocal(
    List<Category> categories, {
    bool clearFirst = false,
  }) async {
    try {
      log(
        '💾 Syncing ${categories.length} categories to local database...',
        name: _logName,
      );
      await _localDataProvider.saveCategoriesBatch(
        categories.map((category) => CategoryDto.fromDomain(category)).toList(),
        clearFirst: clearFirst,
      );
      log('✅ Categories synced to local successfully', name: _logName);
    } catch (e) {
      log('❌ Error syncing categories to local: $e', name: _logName);
      rethrow;
    }
  }

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

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