import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as img;
import 'package:supabase_flutter/supabase_flutter.dart';
import '../main.dart';

/// Handles uploading / deleting car images in Supabase Storage.
///
/// Bucket: `car-images` (private)
/// Path:   `{auth.uid()}/{hotwheels.id}.jpg`
class StorageService {
  StorageService._();

  static const _bucket = 'car-images';
  static const int _maxImageBytes = 500 * 1024;
  static const int _maxWidth = 1080;
  static const int _signedUrlExpirySeconds = 3600;
  static const int _maxCompressionAttempts = 5;
  static const _signedUrlRefreshBuffer = Duration(minutes: 3);
  static const _maxSignedUrlCacheEntries = 500;
  static final Map<String, _SignedUrlCacheEntry> _signedUrlCache = {};

  /// Upload a car image for a specific hotwheels entry.
  /// Returns the storage path on success (e.g. `uid/123.jpg`).
  static Future<String> uploadCarImage({
    required File file,
    required int entryId,
    String? oldPath,
  }) async {
    if (oldPath != null && oldPath.isNotEmpty) {
      await deleteCarImage(oldPath);
    }

    final userId = supabase.auth.currentUser!.id;
    final path = '$userId/$entryId.jpg';
    final compressed = await _compressImage(file);

    await supabase.storage.from(_bucket).uploadBinary(
          path,
          compressed,
          fileOptions: const FileOptions(
            upsert: true,
            contentType: 'image/jpeg',
          ),
        );

    _signedUrlCache.remove(path);

    return path;
  }

  /// Generates a temporary signed URL for a private image path.
  static Future<String?> createSignedUrl(String? path) async {
    if (path == null || path.isEmpty) return null;

    final now = DateTime.now();
    final cached = _signedUrlCache[path];
    if (cached != null &&
        now.isBefore(cached.expiresAt.subtract(_signedUrlRefreshBuffer))) {
      return cached.url;
    }

    try {
      final signed = await supabase.storage
          .from(_bucket)
          .createSignedUrl(path, _signedUrlExpirySeconds);
      _signedUrlCache[path] = _SignedUrlCacheEntry(
        url: signed,
        expiresAt: now.add(const Duration(seconds: _signedUrlExpirySeconds)),
      );
      _pruneSignedUrlCache(now);
      return signed;
    } catch (e) {
      debugPrint('StorageService.createSignedUrl error: $e');
      return null;
    }
  }

  /// Deletes an image using its storage path.
  static Future<void> deleteCarImage(String? path) async {
    if (path == null || path.isEmpty) return;
    _signedUrlCache.remove(path);
    try {
      await supabase.storage.from(_bucket).remove([path]);
    } catch (e) {
      debugPrint('StorageService.deleteCarImage error: $e');
    }
  }

  static void invalidateSignedUrl(String? path) {
    if (path == null || path.isEmpty) return;
    _signedUrlCache.remove(path);
  }

  static void _pruneSignedUrlCache(DateTime now) {
    _signedUrlCache.removeWhere((_, entry) => now.isAfter(entry.expiresAt));

    if (_signedUrlCache.length <= _maxSignedUrlCacheEntries) return;
    final overflow = _signedUrlCache.length - _maxSignedUrlCacheEntries;
    final keys = _signedUrlCache.keys.take(overflow).toList();
    for (final key in keys) {
      _signedUrlCache.remove(key);
    }
  }

  static Future<Uint8List> _compressImage(File source) async {
    final bytes = await source.readAsBytes();
    final decoded = img.decodeImage(bytes);
    if (decoded == null) {
      throw Exception('Invalid image file.');
    }

    img.Image working = decoded.width > _maxWidth
        ? img.copyResize(decoded, width: _maxWidth)
        : decoded;

    var quality = 85;
    var attempts = 0;
    Uint8List out = Uint8List.fromList(img.encodeJpg(working, quality: quality));

    // First pass: reduce JPEG quality.
    while (out.lengthInBytes > _maxImageBytes &&
        quality > 35 &&
        attempts < _maxCompressionAttempts) {
      quality -= 5;
      out = Uint8List.fromList(img.encodeJpg(working, quality: quality));
      attempts += 1;
    }

    // Second pass: reduce dimensions progressively if still above the limit.
    while (out.lengthInBytes > _maxImageBytes &&
        working.width > 320 &&
        attempts < _maxCompressionAttempts) {
      final nextWidth = (working.width * 0.85).round();
      working = img.copyResize(working, width: nextWidth);
      out = Uint8List.fromList(img.encodeJpg(working, quality: quality));
      attempts += 1;
    }

    if (out.lengthInBytes > _maxImageBytes) {
      throw Exception(
        'Image is too large after compression (${out.lengthInBytes} bytes).',
      );
    }

    return out;
  }
}

class _SignedUrlCacheEntry {
  final String url;
  final DateTime expiresAt;

  const _SignedUrlCacheEntry({required this.url, required this.expiresAt});
}
