import 'dart:io';
import 'dart:typed_data';

import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:minstrel/player/album_cover_cache.dart';

class _RecordingAdapter implements HttpClientAdapter {
  _RecordingAdapter(this.body);
  final List<int> body;
  int callCount = 0;

  @override
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
    Future<void>? cancelFuture,
  ) async {
    callCount++;
    return ResponseBody.fromBytes(body, 200, headers: {
      Headers.contentTypeHeader: ['image/jpeg'],
    });
  }

  @override
  void close({bool force = false}) {}
}

class _FailingAdapter implements HttpClientAdapter {
  @override
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
    Future<void>? cancelFuture,
  ) async {
    throw DioException(
      requestOptions: options,
      type: DioExceptionType.connectionError,
      message: 'simulated network failure',
    );
  }

  @override
  void close({bool force = false}) {}
}

Future<Directory> _tmpDirFactory() async =>
    Directory.systemTemp.createTempSync('cover_cache_test_');

void main() {
  test('cache miss writes file and returns path', () async {
    final adapter = _RecordingAdapter([1, 2, 3, 4]);
    final dio = Dio(BaseOptions(baseUrl: 'http://test'))..httpClientAdapter = adapter;
    final cache = AlbumCoverCache(
      dioFactory: () async => dio,
      cacheDirFactory: _tmpDirFactory,
    );
    final path = await cache.getOrFetch('alb-1');
    expect(path, isNotNull);
    expect(File(path!).readAsBytesSync(), [1, 2, 3, 4]);
    expect(adapter.callCount, 1);
  });

  test('cache hit returns same path without re-fetching', () async {
    final adapter = _RecordingAdapter([9, 9, 9]);
    final dio = Dio(BaseOptions(baseUrl: 'http://test'))..httpClientAdapter = adapter;
    final tmp = await _tmpDirFactory();
    final cache = AlbumCoverCache(
      dioFactory: () async => dio,
      cacheDirFactory: () async => tmp,
    );
    final p1 = await cache.getOrFetch('alb-2');
    final p2 = await cache.getOrFetch('alb-2');
    expect(p1, p2);
    expect(adapter.callCount, 1);
  });

  test('concurrent calls for same albumId dedupe', () async {
    final adapter = _RecordingAdapter([1, 2, 3]);
    final dio = Dio(BaseOptions(baseUrl: 'http://test'))..httpClientAdapter = adapter;
    final cache = AlbumCoverCache(
      dioFactory: () async => dio,
      cacheDirFactory: _tmpDirFactory,
    );
    final results = await Future.wait([
      cache.getOrFetch('alb-3'),
      cache.getOrFetch('alb-3'),
      cache.getOrFetch('alb-3'),
    ]);
    expect(results[0], isNotNull);
    expect(results[1], results[0]);
    expect(results[2], results[0]);
    expect(adapter.callCount, 1);
  });

  test('failure returns null without writing file', () async {
    final adapter = _FailingAdapter();
    final dio = Dio(BaseOptions(baseUrl: 'http://test'))..httpClientAdapter = adapter;
    final tmp = await _tmpDirFactory();
    final cache = AlbumCoverCache(
      dioFactory: () async => dio,
      cacheDirFactory: () async => tmp,
    );
    final path = await cache.getOrFetch('alb-4');
    expect(path, isNull);
    expect(File('${tmp.path}/album_covers/alb-4.jpg').existsSync(), isFalse);
  });

  test('empty albumId returns null without dio call', () async {
    final adapter = _RecordingAdapter([1, 2]);
    final dio = Dio(BaseOptions(baseUrl: 'http://test'))..httpClientAdapter = adapter;
    final cache = AlbumCoverCache(
      dioFactory: () async => dio,
      cacheDirFactory: _tmpDirFactory,
    );
    final path = await cache.getOrFetch('');
    expect(path, isNull);
    expect(adapter.callCount, 0);
  });
}
