import 'dart:async';

import 'package:ferry/ferry.dart';
import 'package:gql_http_link/gql_http_link.dart';

import '../../../shared/auth/telegram_session.dart' as telegram_auth;

class MapflowGraphqlClient {
  factory MapflowGraphqlClient({
    Client? client,
    String? telegramInitData,
    String? telegramLoginData,
    String? mapflowSessionToken,
    String endpoint = const String.fromEnvironment(
      'API_BASE_URL',
      defaultValue: '/graphql',
    ),
  }) {
    final resolvedTelegramInitData =
        telegramInitData ?? telegram_auth.telegramInitData();
    final resolvedTelegramLoginData =
        telegramLoginData ?? telegram_auth.telegramLoginData();
    final resolvedMapflowSessionToken =
        mapflowSessionToken ?? telegram_auth.mapflowSessionToken();

    return MapflowGraphqlClient._(
      client:
          client ??
          Client(
            link: HttpLink(
              Uri.base.resolve(endpoint).toString(),
              defaultHeaders: _headers(
                resolvedTelegramInitData,
                resolvedTelegramLoginData,
                resolvedMapflowSessionToken,
              ),
            ),
            defaultFetchPolicies: const {
              OperationType.query: FetchPolicy.NetworkOnly,
              OperationType.mutation: FetchPolicy.NetworkOnly,
              OperationType.subscription: FetchPolicy.NoCache,
            },
          ),
      telegramInitData: resolvedTelegramInitData,
      telegramLoginData: resolvedTelegramLoginData,
      mapflowSessionToken: resolvedMapflowSessionToken,
    );
  }

  MapflowGraphqlClient._({
    required Client client,
    required this.telegramInitData,
    required this.telegramLoginData,
    required this.mapflowSessionToken,
  }) : _client = client;

  final Client _client;
  final String telegramInitData;
  final String telegramLoginData;
  final String mapflowSessionToken;

  bool get hasTelegramAuth =>
      telegramInitData.isNotEmpty ||
      telegramLoginData.isNotEmpty ||
      mapflowSessionToken.isNotEmpty;

  Future<TData> request<TData, TVars>(
    OperationRequest<TData, TVars> request,
  ) async {
    final response = await _client
        .request(request)
        .firstWhere((item) => !item.loading);

    if (response.hasErrors) {
      throw StateError(_errorMessage(response));
    }

    final data = response.data;
    if (data == null) {
      throw StateError('GraphQL response data is empty.');
    }

    return data;
  }

  void dispose() {
    unawaited(_client.dispose());
  }

  static Map<String, String> _headers(
    String telegramInitData,
    String telegramLoginData,
    String mapflowSessionToken,
  ) {
    return {
      if (telegramInitData.isNotEmpty) 'x-telegram-init-data': telegramInitData,
      if (telegramLoginData.isNotEmpty)
        'x-telegram-login-data': telegramLoginData,
      if (mapflowSessionToken.isNotEmpty)
        'x-mapflow-session-token': mapflowSessionToken,
    };
  }

  String _errorMessage(OperationResponse<dynamic, dynamic> response) {
    final graphQLErrors = response.graphqlErrors;
    if (graphQLErrors != null && graphQLErrors.isNotEmpty) {
      return graphQLErrors.map((error) => error.message).join('\n');
    }

    final linkException = response.linkException;
    if (linkException != null) {
      return linkException.toString();
    }

    return 'GraphQL request failed.';
  }
}
