/// A service that handles file storage operations using Firebase Storage.
///
/// This service provides functionality for:
/// - Receipt image upload and compression
/// - Profile image management
/// - File validation and optimization
/// - Automatic image compression to reduce storage costs
/// - Metadata management for uploaded files
///
/// The service automatically compresses images to JPEG format with 85% quality
/// to balance file size and image quality. It also generates unique filenames
/// and handles error logging through the ErrorService.
///
/// Example usage:
/// ```dart
/// final storageService = StorageService();
///
/// // Upload a receipt image
/// final receiptUrl = await storageService.uploadReceiptImage(groupId, imageFile);
///
/// // Upload a profile image
/// final profileUrl = await storageService.uploadProfileImage(userId, imageFile);
///
/// // Delete a file
/// await storageService.deleteFile(fileUrl);
/// ```
library;

import 'dart:io';
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:path/path.dart' as path;
import 'package:image/image.dart' as img;
import 'error_service.dart';

/// Service for managing file storage operations with Firebase Storage.
class StorageService {
  /// Firebase Storage instance for file operations
  final FirebaseStorage _storage;

  /// Service for error handling and logging
  final ErrorService _errorService;

  /// Constructor for StorageService.
  ///
  /// Args:
  ///   [storage]: Optional Firebase Storage instance (auto-created if null)
  ///   [errorService]: Optional error service instance (auto-created if null)
  StorageService({FirebaseStorage? storage, ErrorService? errorService})
    : _storage = storage ?? FirebaseStorage.instance,
      _errorService = errorService ?? ErrorService();

  /// Uploads a receipt image for an expense with automatic compression.
  ///
  /// Validates the image file, compresses it to JPEG format with 85% quality,
  /// generates a unique filename, and uploads it with appropriate metadata.
  /// Monitors upload progress and logs it for debugging purposes.
  ///
  /// Args:
  ///   [groupId]: ID of the group this receipt belongs to
  ///   [imageFile]: The image file to upload
  ///
  /// Returns:
  ///   A `Future<String>` containing the download URL of the uploaded image
  ///
  /// Throws:
  ///   Exception if file validation fails or upload encounters an error
  Future<String> uploadReceiptImage(String groupId, File imageFile) async {
    try {
      // File validation
      _validateImageFile(imageFile);

      // Image compression
      final compressedImage = await _compressImage(imageFile);

      // Generate unique filename
      final fileName = _generateReceiptFileName(groupId);

      // Storage reference
      final storageRef = _storage.ref().child('receipts/$groupId/$fileName');

      // Metadata for cache optimization and compression info
      final metadata = SettableMetadata(
        contentType: 'image/jpeg',
        customMetadata: {
          'groupId': groupId,
          'uploadedAt': DateTime.now().toIso8601String(),
          'compressed': 'true',
        },
      );

      // File upload
      final uploadTask = storageRef.putData(compressedImage, metadata);

      // Progress monitoring (optional)
      uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
        final progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        _errorService.logInfo(
          'StorageService',
          'Upload progress: ${progress.toStringAsFixed(1)}%',
        );
      });

      // Wait for completion
      final snapshot = await uploadTask;

      // Get download URL
      final downloadUrl = await snapshot.ref.getDownloadURL();

      _errorService.logSuccess(
        'StorageService',
        'Image uploaded successfully: $fileName',
      );
      return downloadUrl;
    } catch (e) {
      _errorService.logError('StorageService', 'Error uploading image: $e');
      rethrow;
    }
  }

  /// Deletes a receipt image from storage.
  ///
  /// Extracts the storage reference from the provided URL and deletes the file.
  /// Does not throw errors to avoid blocking expense deletion operations.
  ///
  /// Args:
  ///   [imageUrl]: The download URL of the image to delete
  Future<void> deleteReceiptImage(String imageUrl) async {
    try {
      if (imageUrl.isEmpty) return;

      // Extract reference from URL
      final ref = _storage.refFromURL(imageUrl);
      await ref.delete();

      _errorService.logSuccess('StorageService', 'Image deleted successfully');
    } catch (e) {
      _errorService.logError('StorageService', 'Error deleting image: $e');
      // Don't rethrow to avoid blocking expense deletion
    }
  }

  /// Compresses an image to optimize storage space and upload speed.
  ///
  /// Reads the image file, decodes it, resizes it if too large (max 1024x1024),
  /// and encodes it as JPEG with 85% quality for optimal balance between
  /// file size and image quality.
  ///
  /// Args:
  ///   [imageFile]: The image file to compress
  ///
  /// Returns:
  ///   A `Future<Uint8List>` containing the compressed image bytes
  ///
  /// Throws:
  ///   Exception if the image cannot be decoded or processed
  Future<Uint8List> _compressImage(File imageFile) async {
    try {
      // Read image
      final bytes = await imageFile.readAsBytes();
      img.Image? image = img.decodeImage(bytes);

      if (image == null) {
        throw Exception('Unable to decode image');
      }

      // Resize if image is too large
      const maxWidth = 1024;
      const maxHeight = 1024;

      if (image.width > maxWidth || image.height > maxHeight) {
        image = img.copyResize(
          image,
          width: image.width > image.height ? maxWidth : null,
          height: image.height > image.width ? maxHeight : null,
          interpolation: img.Interpolation.linear,
        );
      }

      // Encode as JPEG with compression
      final compressedBytes = img.encodeJpg(image, quality: 85);

      _errorService.logInfo(
        'StorageService',
        'Image compressed: ${bytes.length} → ${compressedBytes.length} bytes',
      );

      return Uint8List.fromList(compressedBytes);
    } catch (e) {
      _errorService.logError('StorageService', 'Error compressing image: $e');
      // Fallback: return original image if compression fails
      return await imageFile.readAsBytes();
    }
  }

  /// Validates an image file before upload.
  ///
  /// Checks file existence, size constraints (max 10MB), and file extension
  /// to ensure only valid image files are processed for upload.
  ///
  /// Args:
  ///   [imageFile]: The image file to validate
  ///
  /// Throws:
  ///   Exception if validation fails (file doesn't exist, too large, or invalid extension)
  void _validateImageFile(File imageFile) {
    // Check if file exists
    if (!imageFile.existsSync()) {
      throw Exception('Image file does not exist');
    }

    // Check file size (max 10MB)
    const maxSizeBytes = 10 * 1024 * 1024; // 10MB
    final fileSize = imageFile.lengthSync();
    if (fileSize > maxSizeBytes) {
      throw Exception('File size exceeds 10MB limit');
    }

    // Check file extension
    final extension = path.extension(imageFile.path).toLowerCase();
    const allowedExtensions = ['.jpg', '.jpeg', '.png', '.webp'];
    if (!allowedExtensions.contains(extension)) {
      throw Exception('Unsupported image format. Use JPG, PNG or WebP');
    }
  }

  /// Generates a unique filename for a receipt image.
  ///
  /// Creates a filename using timestamp, microseconds, and group ID to ensure
  /// uniqueness and prevent naming conflicts when multiple receipts are uploaded.
  ///
  /// Args:
  ///   [groupId]: ID of the group this receipt belongs to
  ///
  /// Returns:
  ///   A unique filename string for the receipt image
  String _generateReceiptFileName(String groupId) {
    final timestamp = DateTime.now().millisecondsSinceEpoch;
    final random = DateTime.now().microsecond;
    return 'receipt_${groupId}_${timestamp}_$random.jpg';
  }

  /// Uploads multiple images simultaneously (for future features).
  ///
  /// Processes multiple image files in parallel for batch upload scenarios.
  /// Each image is validated, compressed, and uploaded with unique filenames.
  ///
  /// Args:
  ///   [groupId]: ID of the group these images belong to
  ///   [imageFiles]: List of image files to upload
  ///
  /// Returns:
  ///   A `Future<List<String>>` containing download URLs of uploaded images
  Future<List<String>> uploadMultipleImages(
    String groupId,
    List<File> imageFiles,
  ) async {
    final uploadTasks = imageFiles.map(
      (file) => uploadReceiptImage(groupId, file),
    );
    return await Future.wait(uploadTasks);
  }

  /// Récupérer les métadonnées d'une image
  Future<FullMetadata?> getImageMetadata(String imageUrl) async {
    try {
      final ref = _storage.refFromURL(imageUrl);
      return await ref.getMetadata();
    } catch (e) {
      _errorService.logError(
        'StorageService',
        'Erreur récupération metadata: $e',
      );
      return null;
    }
  }

  /// Nettoyer les images orphelines d'un groupe
  Future<void> cleanupGroupImages(String groupId) async {
    try {
      final groupRef = _storage.ref().child('receipts/$groupId');
      final listResult = await groupRef.listAll();

      for (final ref in listResult.items) {
        // Vérifier l'âge du fichier
        final metadata = await ref.getMetadata();
        final uploadDate = metadata.timeCreated;

        if (uploadDate != null) {
          final daysSinceUpload = DateTime.now().difference(uploadDate).inDays;

          // Supprimer les fichiers de plus de 30 jours sans dépense associée
          if (daysSinceUpload > 30) {
            await ref.delete();
            _errorService.logInfo(
              'StorageService',
              'Image orpheline supprimée: ${ref.name}',
            );
          }
        }
      }
    } catch (e) {
      _errorService.logError('StorageService', 'Erreur nettoyage images: $e');
    }
  }

  /// Calculer la taille totale utilisée par un groupe
  Future<int> getGroupStorageSize(String groupId) async {
    try {
      final groupRef = _storage.ref().child('receipts/$groupId');
      final listResult = await groupRef.listAll();

      int totalSize = 0;
      for (final ref in listResult.items) {
        final metadata = await ref.getMetadata();
        totalSize += metadata.size ?? 0;
      }

      return totalSize;
    } catch (e) {
      _errorService.logError(
        'StorageService',
        'Erreur calcul taille storage: $e',
      );
      return 0;
    }
  }
}
