part of 'music_web_server_base.dart';

class _JellyfinServer extends MusicWebServer {
  Uri? _serverUri;
  late _JellyfinClientWrapper _wrapper;

  _JellyfinServer.init(super.authDetails) {
    final uriFull = Uri.parse(authDetails.dir.sourceRaw);
    final uriClean = DirectoryIndexServer.parseWithoutLibraryIdAndClean(authDetails.dir.sourceRaw);

    _serverUri = Uri.parse(uriClean);
    final jellyfinAuth = authDetails.auth.toJellyfinAuthModel();
    final libraryId = uriFull.queryParameters['_libraryId'];
    _wrapper = _JellyfinClientWrapper(
      uriClean,
      jellyfinAuth.username,
      jellyfinAuth.password,
      libraryId,
    );
  }

  @override
  void dispose() {
    _wrapper.dispose();
  }

  @override
  Future<MusicWebServerError?> ping() => _wrapper.ping();

  @override
  Future<Set<ServerShareWrapper>?> getAvailableShares() async => _wrapper.getLibraries();

  @override
  Future<Uint8List?> getImage(String id) => _wrapper.getImage(id);

  @override
  Future<void> fetchAllMusicAndProcess(Map<String, int> serverTracksInLibrary, void Function(TrackExtended trExt) callback, {required bool forceReIndex}) async {
    final wrapper = _wrapper;

    final server = authDetails.dir.toDbKey();
    final serverUriParsed = Uri.parse(server);

    final splitConfig = SplitArtistGenreConfigsWrapper.settings();

    final stream = wrapper.fetchAllMedia(
      batchSize: 400,
      checkResError: (res) => _checkResError(authDetails.dir, res),
    );

    await for (final item in stream) {
      final trExt = _baseItemDtoToTrackExtended(
        item,
        splitConfig: splitConfig,
        server: server,
        serverUriParsed: serverUriParsed,
      );
      callback(trExt);
    }
  }

  @override
  Future<WebStreamUriDetails?> getStreamUrl(String id, {void Function(File cachedFile)? onFetchedIfLocal}) async {
    final baseUri = _serverUri;
    if (baseUri == null) return null;

    await _wrapper.ensureAuthenticated();

    final uri = Uri(
      scheme: baseUri.scheme,
      host: baseUri.host,
      port: baseUri.port,
      userInfo: baseUri.userInfo,
      path: '/Audio/$id/stream',
      queryParameters: {
        'UserId': ?_wrapper._userId,
        'api_key': ?_wrapper._token,
        'static': 'true',
      },
    );
    return WebStreamUriDetails.fromUri(uri);
  }

  bool _checkResError(DirectoryIndex dir, Response<dynamic>? res) {
    final statusCode = res?.statusCode;
    if (statusCode == 401 || statusCode == 403) {
      if (dir is DirectoryIndexServer) {
        MusicWebServerAuthDetails.manager.deleteFromDb(dir);
      }
      return true;
    }
    return false;
  }

  Iterable<String> _splitAll(List<String>? original, List<String> Function(String part) splitter) sync* {
    for (final item in original ?? <String>[]) {
      final parts = splitter(item);
      yield* parts;
    }
  }

  TrackExtended _baseItemDtoToTrackExtended(
    _JellyfinItem item, {
    required SplitArtistGenreConfigsWrapper splitConfig,
    required String server,
    required Uri serverUriParsed,
  }) {
    final id = item.id ?? '';

    final newUri = serverUriParsed.replace(
      queryParameters: {
        ...serverUriParsed.queryParameters,
        'd': id,
      },
    );
    final path = newUri.toString();

    final title = item.name ?? '';
    final album = item.album ?? '';
    final albumsList = Indexer.splitAlbum(
      album,
      config: splitConfig.albumConfig,
    );
    final albumArtist = item.albumArtist ?? '';

    final originalArtist = item.artists.join(', ');
    final artistsList = _splitAll(
      item.artists,
      (p) => Indexer.splitArtist(
        title: title,
        originalArtist: p,
        config: splitConfig.artistsConfig,
      ),
    ).toList();

    final originalGenre = item.genres.join(', ');
    final genresList = _splitAll(
      item.genres,
      (p) => Indexer.splitGenre(
        p,
        config: splitConfig.genresConfig,
      ),
    ).toList();

    final year = item.productionYear ?? 0;
    final yearString = year != 0 ? year.toString() : '';

    final durationMs = item.runTimeTicks != null ? (item.runTimeTicks! ~/ 10000) : 0;
    final mediaSource = item.mediaSources.firstOrNull;
    final bitrate = (mediaSource?.bitrate ?? 0) ~/ 1000;
    final size = mediaSource?.size ?? 0;
    final format = item.container ?? '';
    final dateAddedMs = item.dateCreated?.millisecondsSinceEpoch ?? 0;
    final dateModifiedMs = item.dateModified?.millisecondsSinceEpoch ?? 0;
    final rating = (item.userData?.rating ?? 0);

    final isVideo = item.videoType != null;

    return TrackExtended(
      title: title,
      originalArtist: originalArtist,
      artistsList: artistsList,
      originalAlbum: album,
      albumsList: albumsList,
      albumArtist: albumArtist,
      originalGenre: originalGenre,
      genresList: genresList,
      originalMood: '',
      moodList: [],
      composer: '',
      trackNo: item.indexNumber ?? 0,
      trackTo: 0,
      durationMS: durationMs,
      year: year,
      yearText: yearString,
      size: size,
      dateAdded: dateAddedMs,
      dateModified: dateModifiedMs,
      path: path,
      comment: '',
      description: '',
      synopsis: '',
      bitrate: bitrate,
      sampleRate: 0,
      bits: 0,
      isLossless: null,
      format: format,
      channels: '',
      discNo: item.parentIndexNumber ?? 0,
      discTo: 0,
      language: '',
      lyrics: '',
      label: '',
      bpm: 0,
      rating: rating,
      originalTags: null,
      tagsList: [],
      gainData: null,
      sortInfo: null,
      hashKey: id,
      isVideo: isVideo,
      server: server,
      albumsIdentifiersWrappers: AlbumIdentifierWrapper.fromAlbums(
        albums: albumsList,
        albumArtist: albumArtist,
        year: yearString,
      ),
    );
  }
}

class JellyfinAuth {
  final String username;
  final String password;

  const JellyfinAuth({
    required this.username,
    required this.password,
  });
}

// class _JellyfinClientWrapper {
//   final JellyfinDart _client;
//   final String _username;
//   final String _password;

//   _JellyfinClientWrapper(
//     this._client,
//     this._username,
//     this._password,
//   );

//   String? _userId;
//   String? _token;
//   Completer<bool>? _authCompleter;

//   Future<bool> ensureAuthenticated() async {
//     if (_token != null && _userId != null) return true;

//     if (_authCompleter != null) return _authCompleter!.future;

//     _authCompleter = Completer<bool>();
//     try {
//       final deviceId = await NamidaDeviceInfo.fetchDeviceId() ?? 'namida';
//       final version = VersionWrapper.current?.name ?? '1.0.0';

//       _client.setMediaBrowserAuth(
//         deviceId: deviceId,
//         version: version,
//       );
//       final authResponse = await _client.getUserApi().authenticateUserByName(
//         authenticateUserByName: AuthenticateUserByName(
//           username: _username,
//           pw: _password,
//         ),
//       );
//       final data = authResponse.data;
//       if (data == null) {
//         _authCompleter!.complete(false);
//         _authCompleter = null;
//         return false;
//       }

//       _token = data.accessToken;
//       _userId = data.user?.id;
//       if (_token != null) _client.setToken(_token!);

//       final success = _token != null && _userId != null;
//       _authCompleter!.complete(success);
//       return success;
//     } on DioException catch (_) {
//       _authCompleter!.complete(false);
//       return false;
//     } finally {
//       _authCompleter = null;
//     }
//   }

//   Future<MusicWebServerError?> ping() async {
//     if (!await ensureAuthenticated()) {
//       return MusicWebServerError(
//         code: 0,
//         message: 'Jellyfin authentication failed',
//       );
//     }
//     try {
//       await _client.getSystemApi().getSystemInfo();
//       return null;
//     } on DioException catch (e) {
//       return MusicWebServerError(
//         code: e.response?.statusCode ?? -1,
//         message: e.message ?? 'Unknown error',
//       );
//     }
//   }

//   Future<Uint8List?> getImage(String id) async {
//     if (!await ensureAuthenticated()) return null;
//     try {
//       final res = await _client.getImageApi().getItemImage(
//         itemId: id,
//         imageType: ImageType.primary,
//       );
//       final data = res.data;
//       if (data is Uint8List) return data;
//       return null;
//     } on DioException catch (_) {
//       return null;
//     }
//   }

//   Stream<BaseItemDto> fetchAllMedia({int batchSize = 400, required bool Function(Response<dynamic>? res) checkResError}) async* {
//     final itemsApi = _client.getItemsApi();
//     int offset = 0;

//     while (true) {
//       try {
//         final res = await itemsApi.getItems(
//           userId: _userId,
//           includeItemTypes: [.audio, .video, .musicVideo],
//           recursive: true,
//           startIndex: offset,
//           limit: batchSize,
//           fields: [
//             ItemFields.mediaStreams,
//             ItemFields.mediaSources,
//             ItemFields.genres,
//             ItemFields.dateCreated,
//             ItemFields.overview,
//             ItemFields.path,
//             ItemFields.tags,
//           ],
//         );

//         if (checkResError(res)) {
//           break;
//         }

//         final items = res.data?.items ?? [];
//         for (final item in items) {
//           yield item;
//         }

//         if (items.length < batchSize) break;
//         offset += batchSize;
//       } on DioException catch (e) {
//         if (checkResError(e.response)) {
//           break;
//         }
//       }
//     }
//   }

//   void dispose() {
//     _client.dio.close(force: true);
//   }
// }

class _JellyfinClientWrapper {
  final _JellyfinApi _api;
  final String _username;
  final String _password;
  final String? _libraryId;

  _JellyfinClientWrapper(
    String baseUrl,
    this._username,
    this._password,
    this._libraryId,
  ) : _api = _JellyfinApi(baseUrl);

  String? _userId;
  String? _token;
  String? _deviceId;
  String? _version;
  Completer<bool>? _authCompleter;

  Future<bool> ensureAuthenticated() async {
    if (_token != null && _userId != null) return true;
    if (_authCompleter != null) return _authCompleter!.future;

    _authCompleter = Completer<bool>();
    try {
      _deviceId = await NamidaDeviceInfo.fetchDeviceId() ?? 'namida';
      _version = VersionWrapper.current?.name ?? '1.0.0';

      _api.setAuthHeader(deviceId: _deviceId!, version: _version!);

      final auth = await _api.authenticate(
        username: _username,
        password: _password,
      );

      _token = auth?.accessToken;
      _userId = auth?.userId;

      if (_token != null) {
        _api.setAuthHeader(
          deviceId: _deviceId!,
          version: _version!,
          token: _token,
        );
      }

      final success = _token != null && _userId != null;
      _authCompleter!.complete(success);
      return success;
    } on DioException catch (_) {
      _authCompleter!.complete(false);
      return false;
    } finally {
      _authCompleter = null;
    }
  }

  Future<MusicWebServerError?> ping() async {
    if (!await ensureAuthenticated()) {
      return MusicWebServerError(code: 0, message: 'Jellyfin authentication failed');
    }
    try {
      await _api.getSystemInfo();
      return null;
    } on DioException catch (e) {
      return MusicWebServerError(
        code: e.response?.statusCode ?? -1,
        message: e.message ?? 'Unknown error',
      );
    }
  }

  Future<Uint8List?> getImage(String id) async {
    if (!await ensureAuthenticated()) return null;
    try {
      return await _api.getItemImage(itemId: id, imageType: _JellyfinImageType.primary);
    } on DioException catch (_) {
      return null;
    }
  }

  Future<Set<ServerShareWrapper>> getLibraries() async {
    if (!await ensureAuthenticated()) return {};
    final res = await _api.dio.get<Map<String, dynamic>>(
      '/Users/$_userId/Views',
    );
    final items = (res.data?['Items'] as List<dynamic>?) ?? [];
    return items.map((e) => ServerShareWrapper.fromJellyfinJson(e as Map<String, dynamic>)).toSet();
  }

  Stream<_JellyfinItem> fetchAllMedia({
    int batchSize = 400,
    required bool Function(Response<dynamic>? res) checkResError,
  }) async* {
    int offset = 0;
    while (true) {
      try {
        final res = await _api.dio.get<Map<String, dynamic>>(
          '/Items',
          queryParameters: {
            'UserId': _userId,
            if (_libraryId != null) 'ParentId': _libraryId,
            'IncludeItemTypes': _JellyfinItemKind.values.map((e) => e.value).join(','),
            'Recursive': true,
            'StartIndex': offset,
            'Limit': batchSize,
            'Fields': _JellyfinItemField.values.map((e) => e.value).join(','),
          },
        );

        if (checkResError(res)) break;

        final items = (res.data?['Items'] as List<dynamic>?)?.map((e) => _JellyfinItem.fromJson(e as Map<String, dynamic>)).toList() ?? [];

        for (final item in items) {
          yield item;
        }

        if (items.length < batchSize) break;
        offset += batchSize;
      } on DioException catch (e) {
        if (checkResError(e.response)) break;
      }
    }
  }

  void dispose() => _api.dio.close(force: true);
}
