import 'dart:convert';

import '../domain/place_models.dart';
import 'graphql/__generated__/authenticate_telegram.req.gql.dart';
import 'graphql/__generated__/authenticate_telegram_login.req.gql.dart';
import 'graphql/__generated__/complete_telegram_bot_login.req.gql.dart';
import 'graphql/__generated__/me.req.gql.dart';
import 'graphql/__generated__/start_telegram_bot_login.req.gql.dart';
import 'mapflow_data_mappers.dart';
import 'mapflow_graphql_client.dart';

class AuthRepository {
  const AuthRepository({required MapflowGraphqlClient client})
    : _client = client;

  final MapflowGraphqlClient _client;

  bool get hasTelegramAuth => _client.hasTelegramAuth;

  Future<AppUser?> authenticateTelegram() async {
    if (!hasTelegramAuth) {
      return null;
    }

    if (_client.mapflowSessionToken.isNotEmpty) {
      final data = await _client.request(GMeReq());
      return appUserFromMe(data.me);
    }

    if (_client.telegramLoginData.isNotEmpty) {
      final loginData =
          jsonDecode(_client.telegramLoginData) as Map<String, dynamic>;
      final data = await _client.request(
        GAuthenticateTelegramLoginReq((b) {
          b.vars.input
            ..id = _requiredDouble(loginData, 'id')
            ..first_name = _optionalString(loginData, 'first_name')
            ..last_name = _optionalString(loginData, 'last_name')
            ..username = _optionalString(loginData, 'username')
            ..photo_url = _optionalString(loginData, 'photo_url')
            ..auth_date = _requiredDouble(loginData, 'auth_date')
            ..hash = _requiredString(loginData, 'hash');
        }),
      );

      return appUserFromAuthenticateTelegramLogin(
        data.authenticateTelegramLogin.user,
      );
    }

    final data = await _client.request(
      GAuthenticateTelegramReq((b) {
        b.vars.input.initData = _client.telegramInitData;
      }),
    );
    return appUserFromAuthenticateTelegram(data.authenticateTelegram.user);
  }

  Future<TelegramBotLogin> startTelegramBotLogin() async {
    final data = await _client.request(GStartTelegramBotLoginReq());
    final payload = data.startTelegramBotLogin;
    return TelegramBotLogin(
      token: payload.token,
      botUrl: payload.botUrl,
      expiresAt: DateTime.parse(payload.expiresAt),
    );
  }

  Future<TelegramBotLoginSession> completeTelegramBotLogin(String token) async {
    final data = await _client.request(
      GCompleteTelegramBotLoginReq((b) {
        b.vars.token = token;
      }),
    );
    final payload = data.completeTelegramBotLogin;
    return TelegramBotLoginSession(
      sessionToken: payload.sessionToken,
      user: appUserFromCompleteTelegramBotLogin(payload.user),
    );
  }

  double _requiredDouble(Map<String, dynamic> json, String key) {
    final value = json[key];
    if (value is num) {
      return value.toDouble();
    }

    throw StateError('Telegram login data "$key" must be a number.');
  }

  String _requiredString(Map<String, dynamic> json, String key) {
    final value = json[key];
    if (value is String && value.isNotEmpty) {
      return value;
    }

    throw StateError('Telegram login data "$key" must be a non-empty string.');
  }

  String? _optionalString(Map<String, dynamic> json, String key) {
    final value = json[key];
    if (value == null || value is String) {
      return value as String?;
    }

    throw StateError('Telegram login data "$key" must be a string.');
  }
}
