import 'dart:async';
import 'package:plezy/media/ids.dart';
import 'dart:convert';
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plezy/exceptions/media_server_exceptions.dart';
import 'package:plezy/media/download_resolution.dart';
import 'package:plezy/media/media_backend.dart';

import 'package:plezy/media/media_kind.dart';
import 'package:plezy/services/download_artwork_helpers.dart';
import 'package:plezy/services/download_artwork_service.dart';
import 'package:plezy/services/download_storage_service.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/utils/media_server_http_client.dart';

import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';

class _DelayedCountingHttpClient extends http.BaseClient {
  _DelayedCountingHttpClient(this.body);

  final List<int> body;
  final release = Completer<void>();
  int sends = 0;

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    sends++;
    await release.future;
    return http.StreamedResponse(Stream<List<int>>.value(body), 200, request: request);
  }
}

void main() {
  late Directory tmpRoot;
  late PathProviderPlatform previousPathProvider;

  setUp(() async {
    resetSharedPreferencesForTest();
    SettingsService.resetForTesting();
    DownloadStorageService.resetForTesting();
    tmpRoot = await Directory.systemTemp.createTemp('download_artwork_service_test_');
    previousPathProvider = PathProviderPlatform.instance;
    PathProviderPlatform.instance = FakePathProvider(tmpRoot);
  });

  tearDown(() async {
    DownloadStorageService.resetForTesting();
    SettingsService.resetForTesting();
    PathProviderPlatform.instance = previousPathProvider;
    expect(PathProviderPlatform.instance, same(previousPathProvider));
    if (await tmpRoot.exists()) await tmpRoot.delete(recursive: true);
  });

  test('buildArtworkSpecs includes all standard artwork with sanitized local keys', () {
    final item = testMediaItem(
      id: 'item-1',
      backend: MediaBackend.jellyfin,
      kind: MediaKind.movie,
      serverId: 'srv',
      thumbPath: 'https://jf/Items/1/Images/Primary?tag=p&api_key=secret',
      clearLogoPath: 'https://jf/Items/1/Images/Logo?tag=l&api_key=secret',
      artPath: 'https://jf/Items/1/Images/Backdrop/0?tag=b&api_key=secret',
      backgroundSquarePath: 'https://jf/Items/1/Images/Thumb?tag=s&api_key=secret',
    );

    final specs = buildArtworkSpecs(item, (path) => path);

    expect(specs, hasLength(4));
    expect(specs.map((spec) => spec.localKey), everyElement(isNot(contains('api_key'))));
    expect(specs.map((spec) => spec.url), everyElement(contains('api_key=secret')));
  });

  test('local paths normalize tokenized Jellyfin URLs', () async {
    final settings = await SettingsService.getInstance();
    final storage = DownloadStorageService.instance;
    await storage.initialize(settings);
    final service = DownloadArtworkService(
      storageService: storage,
      http: MediaServerHttpClient(client: FakeHttpClient(200, utf8.encode('image'))),
    );

    const tokenized = 'https://jf/Items/1/Images/Logo?tag=abc&api_key=secret';
    const sanitized = 'https://jf/Items/1/Images/Logo?tag=abc';

    expect(await service.localPath(ServerId('srv'), tokenized), await service.localPath(ServerId('srv'), sanitized));
  });

  test('downloadFile rejects non-success responses without leaving final files', () async {
    final file = File(p.join(tmpRoot.path, 'art.jpg'));
    final httpClient = MediaServerHttpClient(client: FakeHttpClient(404, utf8.encode('not found')));

    await expectLater(
      httpClient.downloadFile('https://example.test/art.jpg', file.path),
      throwsA(isA<MediaServerHttpException>()),
    );

    expect(file.existsSync(), isFalse);
    expect(File('${file.path}.download').existsSync(), isFalse);
  });

  test('downloadSingleArtwork replaces unusable existing files', () async {
    final settings = await SettingsService.getInstance();
    final storage = DownloadStorageService.instance;
    await storage.initialize(settings);
    final body = utf8.encode('valid image bytes');
    final service = DownloadArtworkService(
      storageService: storage,
      http: MediaServerHttpClient(client: FakeHttpClient(200, body)),
    );

    const rawPath = 'https://jf/Items/1/Images/Logo?tag=abc&api_key=secret';
    final filePath = await service.localPath(ServerId('srv'), rawPath);
    await File(filePath).writeAsString('<html>not an image</html>');

    expect(
      await service.downloadSingleArtwork(
        ServerId('srv'),
        DownloadArtworkSpec(localKey: artworkStorageKey(rawPath), url: 'https://example.test/logo.png'),
      ),
      isTrue,
    );

    expect(await File(filePath).readAsBytes(), body);
    expect(await service.existsUsable(ServerId('srv'), rawPath), isTrue);
  });

  test('artwork settlement reports HTTP and invalid-image failures', () async {
    final settings = await SettingsService.getInstance();
    final storage = DownloadStorageService.instance;
    await storage.initialize(settings);
    final missingService = DownloadArtworkService(
      storageService: storage,
      http: MediaServerHttpClient(client: FakeHttpClient(404, utf8.encode('not found'))),
    );
    final invalidService = DownloadArtworkService(
      storageService: storage,
      http: MediaServerHttpClient(client: FakeHttpClient(200, utf8.encode('<html>error</html>'))),
    );

    final missingSettled = await missingService.ensureArtworkSpecs(ServerId('srv'), const [
      DownloadArtworkSpec(localKey: '/missing.jpg', url: 'https://example.test/missing.jpg'),
    ]);
    final invalidSettled = await invalidService.ensureArtworkSpecs(ServerId('srv'), const [
      DownloadArtworkSpec(localKey: '/invalid.jpg', url: 'https://example.test/invalid.jpg'),
    ]);

    expect(missingSettled, isFalse);
    expect(invalidSettled, isFalse);
    expect(await missingService.existsUsable(ServerId('srv'), '/missing.jpg'), isFalse);
    expect(await invalidService.existsUsable(ServerId('srv'), '/invalid.jpg'), isFalse);
  });

  test('downloadSingleArtwork serializes duplicate writes to the same local file', () async {
    final settings = await SettingsService.getInstance();
    final storage = DownloadStorageService.instance;
    await storage.initialize(settings);
    final httpClient = _DelayedCountingHttpClient(utf8.encode('valid image bytes'));
    final service = DownloadArtworkService(
      storageService: storage,
      http: MediaServerHttpClient(client: httpClient),
    );
    const rawPath = 'https://jf/Items/1/Images/Logo?tag=abc&api_key=secret';
    final spec = DownloadArtworkSpec(localKey: artworkStorageKey(rawPath), url: 'https://example.test/logo.png');

    final first = service.downloadSingleArtwork(ServerId('srv'), spec);
    await Future<void>.delayed(Duration.zero);
    final second = service.downloadSingleArtwork(ServerId('srv'), spec);
    await Future<void>.delayed(Duration.zero);
    httpClient.release.complete();

    expect(await Future.wait([first, second]), everyElement(isTrue));

    expect(httpClient.sends, 1);
    expect(await service.existsUsable(ServerId('srv'), rawPath), isTrue);
  });
}
