import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:dio/dio.dart';
import 'package:flame/components.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';

import '../config.dart';

/// تصاویرِ کارت از backend می‌آیند (نه bundle داخل اپ) تا حجمِ اپ کم بماند، اما هر
/// تصویر فقط **یک‌بار برای همیشه** دانلود می‌شود: بایت‌ها روی دیسکِ ماندگار ذخیره و
/// در جلسه‌های بعد از دیسک خوانده می‌شوند (بدونِ شبکه). یک کشِ درون‌حافظه‌ایِ
/// همگام ([_ready]) هم نگه می‌داریم تا کارت‌ها بدونِ پرش/انتظار رندر شوند.
///
/// مسیرِ سرور: `/cards/<skin>/<code>.png` و `/cards/<skin>/back.jpg`.
class CardImages {
  CardImages._();

  static const _fallbackSkin = 'simple';
  static final Dio _dio = Dio(BaseOptions(
    responseType: ResponseType.bytes,
    connectTimeout: const Duration(seconds: 10),
    receiveTimeout: const Duration(seconds: 12),
    validateStatus: (s) => s != null && s >= 200 && s < 300,
  ));

  // اسپرایتِ آماده و decode‌شده (کلید: "<skin>/<file>") — دسترسیِ همگام برای رندرِ فوری.
  static final Map<String, Sprite> _ready = {};
  // Futureهای در حالِ اجرا/تمام‌شده برای جلوگیری از بارگذاریِ تکراری.
  static final Map<String, Future<Sprite?>> _inflight = {};
  static Future<String>? _dir;

  static String _url(String skin, String file) =>
      '${AppConfig.baseUrl}/cards/$skin/$file';

  // ===== دسترسیِ همگام (اگر از قبل در حافظه آماده باشد) =====

  /// اسپرایتِ رویِ کارت اگر همین حالا در حافظه آماده باشد، وگرنه null.
  static Sprite? cachedFront(String skin, String code) => _ready['$skin/$code.png'];

  /// اسپرایتِ پشتِ کارت اگر در حافظه آماده باشد، وگرنه null.
  static Sprite? cachedBack(String skin) => _ready['$skin/back.jpg'];

  // ===== بارگذاریِ ناهمگام (دیسک → شبکه) =====

  /// اسپرایتِ رویِ یک کارت (مثل "AS")؛ در صورت خطا null (کشیدنِ برداری در CardComponent).
  static Future<Sprite?> front(String skin, String code) =>
      _load(skin, '$code.png');

  /// اسپرایتِ پشتِ کارت برای اسکین.
  static Future<Sprite?> back(String skin) => _load(skin, 'back.jpg');

  /// کلِ دسته (۵۲ کارت + پشت) را گرم می‌کند: از دیسک (فوری) یا در اولین بار از شبکه.
  /// fire-and-forget؛ در طولِ صفحه‌ی «جستجوی حریف» اجرا می‌شود تا هنگامِ پخش آماده باشند.
  static void preloadDeck(String skin) {
    const ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
    const suits = ['S', 'H', 'D', 'C'];
    for (final s in suits) {
      for (final r in ranks) {
        _load(skin, '$r$s.png');
      }
    }
    _load(skin, 'back.jpg');
  }

  static Future<Sprite?> _load(String skin, String file) {
    final key = '$skin/$file';
    final ready = _ready[key];
    if (ready != null) return Future.value(ready);
    return _inflight.putIfAbsent(key, () => _fetch(skin, file));
  }

  static Future<Sprite?> _fetch(String skin, String file) async {
    var s = await _fromDiskOrNet(skin, file);
    if (s == null && skin != _fallbackSkin) {
      s = await _fromDiskOrNet(_fallbackSkin, file);
    }
    if (s != null) _ready['$skin/$file'] = s;
    return s;
  }

  static Future<Sprite?> _fromDiskOrNet(String skin, String file) async {
    // ۱) دیسکِ ماندگار (اگر قبلاً دانلود شده).
    final disk = await _readDisk(skin, file);
    if (disk != null) {
      final sp = await _toSprite(disk);
      if (sp != null) return sp;
    }
    // ۲) شبکه — سپس ذخیره روی دیسک برای دفعه‌ی بعد.
    try {
      final res = await _dio.get<List<int>>(_url(skin, file));
      final data = res.data;
      if (data == null || data.isEmpty) return null;
      final bytes = Uint8List.fromList(data);
      unawaited(_writeDisk(skin, file, bytes));
      return _toSprite(bytes);
    } catch (e) {
      debugPrint('CardImages: $skin/$file failed: $e');
      return null;
    }
  }

  /// تصویرِ فرش از URL کاملِ backend (کش‌شده در حافظه با کلیدِ URL).
  static Future<Sprite?> carpetByUrl(String url) {
    if (url.isEmpty) return Future.value(null);
    final key = 'carpet/$url';
    final ready = _ready[key];
    if (ready != null) return Future.value(ready);
    return _inflight.putIfAbsent(key, () async {
      try {
        final res = await _dio.get<List<int>>(url);
        final data = res.data;
        if (data == null || data.isEmpty) return null;
        final sp = await _toSprite(Uint8List.fromList(data));
        if (sp != null) _ready[key] = sp;
        return sp;
      } catch (e) {
        debugPrint('CardImages: carpet $url failed: $e');
        return null;
      }
    });
  }

  // ===== دیسک =====

  static Future<String> _cacheDir() =>
      _dir ??= getApplicationSupportDirectory().then((d) => '${d.path}/hakem_cards');

  static Future<Uint8List?> _readDisk(String skin, String file) async {
    try {
      final f = File('${await _cacheDir()}/$skin/$file');
      if (await f.exists()) return f.readAsBytes();
    } catch (_) {}
    return null;
  }

  static Future<void> _writeDisk(String skin, String file, Uint8List bytes) async {
    try {
      final f = File('${await _cacheDir()}/$skin/$file');
      await f.parent.create(recursive: true);
      await f.writeAsBytes(bytes, flush: false);
    } catch (_) {}
  }

  static Future<Sprite?> _toSprite(Uint8List bytes) async {
    try {
      final codec = await ui.instantiateImageCodec(bytes);
      final frame = await codec.getNextFrame();
      return Sprite(frame.image);
    } catch (_) {
      return null;
    }
  }
}
