import 'package:logging/logging.dart';

import '../models/change.dart';
import '../models/sheet.dart';
import 'api_client.dart';
import 'storage_service.dart';

/// Result of a sync operation.
class SyncResult {
  final List<Sheet> sheets;
  final bool isOnline;
  final int changesSynced;
  final int annotationsSynced;
  final int changesUnsynced;
  final int annotationsUnsynced;

  SyncResult({
    required this.sheets,
    required this.isOnline,
    required this.changesSynced,
    required this.annotationsSynced,
    required this.changesUnsynced,
    required this.annotationsUnsynced,
  });
}

/// Service for coordinating offline/online synchronization.
///
/// Handles:
/// - Fetching sheets with offline fallback to cached data
/// - Uploading pending changes when connection is available
/// - Uploading pending annotation uploads
/// - Applying local changes to sheets list
class SyncService {
  final _log = Logger('SyncService');
  final ApiClient _apiClient;
  final StorageService _storageService;

  SyncService({
    required ApiClient apiClient,
    required StorageService storageService,
  }) : _apiClient = apiClient,
       _storageService = storageService;

  /// Performs a full sync operation.
  ///
  /// 1. Checks if online
  /// 2. If online: fetches sheets, uploads pending changes, uploads pending annotations
  /// 3. If offline: loads cached sheets and applies pending changes locally
  ///
  /// Returns [SyncResult] with the sheets list and sync status.
  Future<SyncResult> sync() async {
    final isOnline = await _apiClient.checkConnection();

    if (isOnline) {
      return _syncOnline();
    } else {
      return _syncOffline();
    }
  }

  /// Online sync: fetch from server, upload pending data.
  Future<SyncResult> _syncOnline() async {
    _log.info('Online sync starting...');

    int changesSynced = 0;
    int annotationsSynced = 0;

    // 1. Fetch fresh sheets from server
    List<Sheet> sheets;
    try {
      sheets = await _apiClient.fetchSheets();
      _log.info('Fetched ${sheets.length} sheets from server');

      // Cache the fetched sheets
      await _storageService.writeCachedSheets(sheets);
    } catch (e) {
      _log.warning('Failed to fetch sheets, falling back to cache: $e');
      return _syncOffline();
    }

    // 2. Upload pending changes
    changesSynced = await _uploadPendingChanges();

    // 3. Upload pending annotations
    annotationsSynced = await _uploadPendingAnnotations();
    final remainingAnnotations = await _storageService
        .readPendingAnnotationUploads();

    // 4. Apply any remaining local changes (in case some failed to upload)
    final changeQueue = await _storageService.readChangeQueue();
    if (changeQueue.isNotEmpty) {
      try {
        changeQueue.applyToSheets(sheets);
        // Update cache with applied changes
        await _storageService.writeCachedSheets(sheets);
      } catch (e) {
        _log.warning('Failed to apply remaining changes: $e');
      }
    }

    _log.info(
      'Online sync complete: $changesSynced changes, $annotationsSynced annotations synced',
    );

    return SyncResult(
      sheets: sheets,
      isOnline: true,
      changesSynced: changesSynced,
      annotationsSynced: annotationsSynced,
      changesUnsynced: changeQueue.length,
      annotationsUnsynced: remainingAnnotations.length,
    );
  }

  /// Offline sync: use cached data with local changes applied.
  Future<SyncResult> _syncOffline() async {
    _log.info('Offline mode: loading cached data...');

    // 1. Load cached sheets
    var sheets = await _storageService.readCachedSheets();

    if (sheets.isEmpty) {
      _log.warning('No cached sheets available in offline mode');
    }

    // 2. Apply pending changes locally
    final changeQueue = await _storageService.readChangeQueue();
    if (changeQueue.isNotEmpty) {
      _log.info('Applying ${changeQueue.length} pending changes locally');
      try {
        changeQueue.applyToSheets(sheets);
      } catch (e) {
        _log.warning('Failed to apply some changes: $e');
      }
    }

    final remainingAnnotations = await _storageService
        .readPendingAnnotationUploads();

    return SyncResult(
      sheets: sheets,
      isOnline: false,
      changesSynced: 0,
      annotationsSynced: 0,
      changesUnsynced: changeQueue.length,
      annotationsUnsynced: remainingAnnotations.length,
    );
  }

  /// Uploads all pending changes to the server.
  ///
  /// Returns the number of successfully synced changes.
  Future<int> _uploadPendingChanges() async {
    final changes = await _storageService.readChangeList();
    if (changes.isEmpty) return 0;

    _log.info('Uploading ${changes.length} pending changes...');

    try {
      final appliedIndices = await _apiClient.uploadChanges(changes);

      // Delete successfully synced changes (in reverse order to maintain indices)
      for (int i = appliedIndices.length - 1; i >= 0; i--) {
        await _storageService.deleteOldestChange();
      }

      _log.info('${appliedIndices.length} changes synced successfully');
      return appliedIndices.length;
    } catch (e) {
      _log.warning('Failed to upload changes: $e');
      return 0;
    }
  }

  /// Uploads all pending annotation uploads to the server.
  ///
  /// Returns the number of successfully synced annotations.
  Future<int> _uploadPendingAnnotations() async {
    final pendingUploads = await _storageService.readPendingAnnotationUploads();
    if (pendingUploads.isEmpty) return 0;

    _log.info('Uploading ${pendingUploads.length} pending annotations...');

    int syncedCount = 0;

    for (final upload in pendingUploads) {
      try {
        await _apiClient.uploadAnnotation(
          sheetUuid: upload.sheetUuid,
          page: upload.page,
          lastModified: upload.lastModified,
          annotationsJson: upload.annotationsJson,
        );

        // Delete from pending queue after successful upload
        await _storageService.deletePendingAnnotationUpload(upload.key);
        syncedCount++;
      } catch (e) {
        _log.warning(
          'Failed to upload annotation for ${upload.sheetUuid} page ${upload.page}: $e',
        );
        // Continue with other uploads
      }
    }

    _log.info('$syncedCount annotations synced successfully');
    return syncedCount;
  }

  /// Queues a change for sync.
  ///
  /// If online, attempts immediate upload. Otherwise, stores locally.
  Future<void> queueChange(Change change) async {
    // Always store locally first
    await _storageService.writeChange(change);

    // Try to upload immediately if online
    try {
      final isOnline = await _apiClient.checkConnection();
      if (isOnline) {
        final changes = await _storageService.readChangeList();
        final appliedIndices = await _apiClient.uploadChanges(changes);

        // Delete synced changes
        for (int i = 0; i < appliedIndices.length; i++) {
          await _storageService.deleteOldestChange();
        }
      }
    } catch (e) {
      _log.fine('Immediate upload failed, change queued for later: $e');
    }
  }

  /// Queues an annotation upload.
  ///
  /// If the upload fails (e.g., offline), it will be stored for later sync.
  Future<bool> uploadAnnotationWithFallback({
    required String sheetUuid,
    required int page,
    required String annotationsJson,
    required DateTime lastModified,
  }) async {
    try {
      await _apiClient.uploadAnnotation(
        sheetUuid: sheetUuid,
        page: page,
        lastModified: lastModified,
        annotationsJson: annotationsJson,
      );
      return true;
    } catch (e) {
      _log.fine('Annotation upload failed, queuing for later: $e');

      // Store for later upload
      await _storageService.writePendingAnnotationUpload(
        PendingAnnotationUpload(
          sheetUuid: sheetUuid,
          page: page,
          annotationsJson: annotationsJson,
          lastModified: lastModified,
        ),
      );
      return false;
    }
  }

  /// Updates the local cache after a sheet edit.
  ///
  /// Call this after applying changes to the sheets list locally.
  Future<void> updateCachedSheets(List<Sheet> sheets) async {
    await _storageService.writeCachedSheets(sheets);
  }

  /// Gets the number of pending changes.
  Future<int> getPendingChangesCount() async {
    return _storageService.getChangeQueueLength();
  }

  /// Gets the number of pending annotation uploads.
  Future<int> getPendingAnnotationsCount() async {
    final uploads = await _storageService.readPendingAnnotationUploads();
    return uploads.length;
  }

  /// Checks if there is any pending data to sync.
  Future<bool> hasPendingData() async {
    final changesCount = await getPendingChangesCount();
    final annotationsCount = await getPendingAnnotationsCount();
    return changesCount > 0 || annotationsCount > 0;
  }
}
