import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:userfront/i18n.dart';
import 'http_client.dart';
import 'auth_token_store.dart';
import 'log_policy.dart';
import 'runtime_env.dart';

typedef AuthProxyHttpClientFactory =
    http.Client Function({bool withCredentials});

class AuthProxyService {
  static AuthProxyHttpClientFactory? _httpClientFactoryForTesting;

  static void debugSetHttpClientFactoryForTesting(
    AuthProxyHttpClientFactory? factory,
  ) {
    _httpClientFactoryForTesting = factory;
  }

  static String get _baseUrl => runtimeBackendUrl();

  static bool get _isProd {
    final env = envOrDefault('APP_ENV', 'dev').toLowerCase();
    return LogPolicy.isProductionEnv(env);
  }

  static bool get isProdEnv => _isProd;
  static bool _shouldSendDrySend(bool? drySend) {
    if (_isProd) {
      return false;
    }
    return drySend == true;
  }

  static Exception _error(String key, String fallback, {String? detail}) {
    final params = detail != null ? {'error': detail} : null;
    var message = tr(key, fallback: fallback, params: params);
    if (message == key && fallback.isNotEmpty) {
      message = _interpolateFallback(fallback, params);
    }
    return Exception(message);
  }

  static String _interpolateFallback(
    String fallback,
    Map<String, String>? params,
  ) {
    var message = fallback;
    params?.forEach((key, value) {
      message = message.replaceAll('{{$key}}', value);
    });
    return message;
  }

  static String _responseErrorDetail(http.Response response) {
    final body = response.body.trim();
    if (body.isEmpty) {
      return 'HTTP ${response.statusCode}';
    }
    try {
      final decoded = jsonDecode(body);
      if (decoded is Map<String, dynamic>) {
        final value = decoded['error'] ?? decoded['message'];
        if (value is String && value.trim().isNotEmpty) {
          return _formatUserPolicyError(value);
        }
      }
    } catch (_) {
      // Fall back to the raw response body.
    }
    return _formatUserPolicyError(body);
  }

  static String _formatUserPolicyError(String message) {
    final normalized = message.toLowerCase();
    if (normalized.contains(
          'internal email domain cannot be assigned to personal tenant',
        ) ||
        normalized.contains('내부 도메인 사용자는 개인 소속으로 생성하거나 변경할 수 없습니다')) {
      return '내부 도메인 사용자는 개인 소속으로 생성하거나 변경할 수 없습니다. 대표소속을 회사 또는 조직 소속으로 지정해 주세요.';
    }
    return message;
  }

  static http.Client _createClient({bool withCredentials = false}) {
    final factory = _httpClientFactoryForTesting;
    if (factory != null) {
      return factory(withCredentials: withCredentials);
    }
    return createHttpClient(withCredentials: withCredentials);
  }

  static Future<http.Response> _get(
    Uri url, {
    Map<String, String>? headers,
    bool withCredentials = false,
  }) async {
    final client = _createClient(withCredentials: withCredentials);
    try {
      return await client.get(url, headers: headers);
    } finally {
      client.close();
    }
  }

  static Future<http.Response> _post(
    Uri url, {
    Map<String, String>? headers,
    Object? body,
    bool withCredentials = false,
  }) async {
    final client = _createClient(withCredentials: withCredentials);
    try {
      return await client.post(url, headers: headers, body: body);
    } finally {
      client.close();
    }
  }

  static Future<http.Response> _patch(
    Uri url, {
    Map<String, String>? headers,
    Object? body,
    bool withCredentials = false,
  }) async {
    final client = _createClient(withCredentials: withCredentials);
    try {
      return await client.patch(url, headers: headers, body: body);
    } finally {
      client.close();
    }
  }

  static Future<http.Response> _delete(
    Uri url, {
    Map<String, String>? headers,
    bool withCredentials = false,
  }) async {
    final client = _createClient(withCredentials: withCredentials);
    try {
      return await client.delete(url, headers: headers);
    } finally {
      client.close();
    }
  }

  static Future<Map<String, dynamic>> fetchPasswordPolicy() async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/password/policy');
    final response = await _get(url);
    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.password_policy_fetch',
        'Failed to load the password policy.',
      );
    }
  }

  static Future<Map<String, dynamic>> checkCookieSession() async {
    final url = Uri.parse('$_baseUrl/api/v1/user/me');
    final response = await _get(
      url,
      headers: {'Content-Type': 'application/json'},
      withCredentials: true,
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    throw _error(
      'err.userfront.auth_proxy.profile_load',
      'Failed to load the profile: {{error}}',
      detail: response.body,
    );
  }

  static Future<Map<String, dynamic>> getMe({
    String? token,
    bool useCookie = true,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/user/me');
    final headers = <String, String>{'Content-Type': 'application/json'};
    if (!useCookie && token != null && token.isNotEmpty) {
      headers['Authorization'] = 'Bearer $token';
    }
    final response = await _get(
      url,
      headers: headers,
      withCredentials: useCookie,
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    throw _error(
      'err.userfront.auth_proxy.profile_load',
      'Failed to load the profile: {{error}}',
      detail: response.body,
    );
  }

  static Future<int> getSessionStatus({
    String? token,
    bool useCookie = false,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/user/me');
    final headers = <String, String>{'Content-Type': 'application/json'};
    if (!useCookie && token != null && token.isNotEmpty) {
      headers['Authorization'] = 'Bearer $token';
    }
    final response = await _get(
      url,
      headers: headers,
      withCredentials: useCookie,
    );
    return response.statusCode;
  }

  static Future<Map<String, dynamic>> getTenantInfo() async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/tenant-info');
    final response = await _get(url);
    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.tenant_info_fetch',
        'Failed to load tenant information.',
      );
    }
  }

  static Future<Map<String, dynamic>> initEnchantedLink(
    String loginId, {
    String? method,
    bool? codeOnly,
    bool? drySend,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/enchanted-link/init');
    final userfrontUrl = runtimeUserfrontUrl();

    final body = <String, dynamic>{'loginId': loginId, 'uri': userfrontUrl};
    if (_shouldSendDrySend(drySend)) {
      body['drySend'] = true;
    }
    if (method != null) {
      body['method'] = method;
    }
    if (codeOnly == true) {
      body['codeOnly'] = true;
    }

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(body),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.login_init',
        'Failed to initialize login: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> pollEnchantedLink(
    String pendingRef,
  ) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/enchanted-link/poll');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'pendingRef': pendingRef}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    if (response.statusCode == 400) {
      return jsonDecode(response.body);
    }
    throw _error(
      'err.userfront.auth_proxy.login_poll',
      'Failed to check login status: {{error}}',
      detail: response.body,
    );
  }

  static Future<Map<String, dynamic>> verifyMagicLink(
    String token, {
    bool verifyOnly = false,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/magic-link/verify');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'token': token, 'verifyOnly': verifyOnly}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.verify_failed',
        'Verification failed: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> verifyLoginCode(
    String loginId,
    String code, {
    String? pendingRef,
    bool verifyOnly = false,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/login/code/verify');

    final payload = {
      'loginId': loginId,
      'code': code,
      'verifyOnly': verifyOnly,
    };
    if (pendingRef != null && pendingRef.isNotEmpty) {
      payload['pendingRef'] = pendingRef;
    }

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(payload),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.verify_failed',
        'Verification failed: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<void> revokeSession(String sessionId) async {
    final url = Uri.parse('$_baseUrl/api/v1/user/sessions/$sessionId');
    final useCookie = AuthTokenStore.usesCookie();
    final token = AuthTokenStore.getToken();
    final headers = <String, String>{'Content-Type': 'application/json'};
    if (!useCookie && token != null && token.isNotEmpty) {
      headers['Authorization'] = 'Bearer $token';
    }
    final response = await _delete(
      url,
      headers: headers,
      withCredentials: useCookie,
    );
    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.dashboard.sessions.revoke',
        'Failed to revoke the session: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<String?> fetchCurrentSessionId() async {
    final url = Uri.parse('$_baseUrl/api/v1/user/sessions');
    final useCookie = AuthTokenStore.usesCookie();
    final token = AuthTokenStore.getToken();
    final headers = <String, String>{'Content-Type': 'application/json'};
    if (!useCookie && token != null && token.isNotEmpty) {
      headers['Authorization'] = 'Bearer $token';
    }
    final response = await _get(
      url,
      headers: headers,
      withCredentials: useCookie,
    );
    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.dashboard.sessions.load',
        'Failed to load the active sessions: {{error}}',
        detail: response.body,
      );
    }

    final body = jsonDecode(response.body) as Map<String, dynamic>;
    final items = (body['items'] as List?) ?? const [];
    for (final item in items.whereType<Map<String, dynamic>>()) {
      if (item['is_current'] == true) {
        final sessionId = item['session_id']?.toString().trim() ?? '';
        if (sessionId.isNotEmpty) {
          return sessionId;
        }
      }
    }
    return null;
  }

  static Future<Map<String, dynamic>> verifyLoginShortCode(
    String shortCode, {
    bool verifyOnly = false,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/login/code/verify-short');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'shortCode': shortCode, 'verifyOnly': verifyOnly}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.verify_failed',
        'Verification failed: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> loginWithPassword(
    String loginId,
    String password, {
    String? loginChallenge,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/password/login');

    final payload = {
      'loginId': loginId,
      'password': password,
      if (loginChallenge != null && loginChallenge.isNotEmpty)
        'login_challenge': loginChallenge,
    };

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(payload),
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return data;
    } else {
      final errorBody = jsonDecode(response.body);
      throw Exception(
        errorBody['error'] ?? tr('err.userfront.auth_proxy.login_failed'),
      );
    }
  }

  static Future<Map<String, dynamic>> getConsentInfo(
    String consentChallenge,
  ) async {
    final url = Uri.parse(
      '$_baseUrl/api/v1/auth/consent',
    ).replace(queryParameters: {'consent_challenge': consentChallenge});
    final response = await _get(
      url,
      headers: {'Content-Type': 'application/json'},
      withCredentials: true,
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      final errorBody = jsonDecode(response.body);
      final rawDetails = errorBody['details'];
      throw AuthProxyException(
        errorCode: (errorBody['code'] ?? '').toString(),
        message:
            (errorBody['error'] ?? tr('err.userfront.auth_proxy.consent_fetch'))
                .toString(),
        details: rawDetails is Map<String, dynamic> ? rawDetails : null,
      );
    }
  }

  static Future<Map<String, dynamic>> acceptConsent(
    String consentChallenge, {
    List<String>? grantScope,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/consent/accept');
    final body = <String, dynamic>{'consent_challenge': consentChallenge};
    if (grantScope != null) {
      body['grant_scope'] = grantScope;
    }

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(body),
      withCredentials: true,
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      final errorBody = jsonDecode(response.body);
      throw Exception(
        errorBody['error'] ?? tr('err.userfront.auth_proxy.consent_accept'),
      );
    }
  }

  static Future<Map<String, dynamic>> rejectConsent(
    String consentChallenge,
  ) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/consent/reject');
    final body = <String, dynamic>{'consent_challenge': consentChallenge};

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(body),
      withCredentials: true,
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      final errorBody = jsonDecode(response.body);
      throw Exception(
        errorBody['error'] ?? tr('err.userfront.auth_proxy.consent_reject'),
      );
    }
  }

  static Future<Map<String, dynamic>> acceptOidcLogin(
    String loginChallenge, {
    String? token,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/oidc/login/accept');
    final headers = <String, String>{'Content-Type': 'application/json'};
    if (token != null && token.isNotEmpty) {
      headers['Authorization'] = 'Bearer $token';
    }
    final response = await _post(
      url,
      headers: headers,
      body: jsonEncode({'login_challenge': loginChallenge}),
      withCredentials: true,
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      final errorBody = jsonDecode(response.body);
      final rawDetails = errorBody['details'];
      throw AuthProxyException(
        errorCode: (errorBody['code'] ?? '').toString(),
        message:
            (errorBody['error'] ?? tr('err.userfront.auth_proxy.oidc_accept'))
                .toString(),
        details: rawDetails is Map<String, dynamic> ? rawDetails : null,
      );
    }
  }

  static Future<Map<String, dynamic>> initiatePasswordReset(
    String loginId, {
    bool? drySend,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/password/reset/initiate');
    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'loginId': loginId,
        if (_shouldSendDrySend(drySend)) 'drySend': true,
      }),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      final errorBody = jsonDecode(response.body);
      throw Exception(
        errorBody['error'] ??
            tr('err.userfront.auth_proxy.password_reset_init'),
      );
    }
  }

  static Future<Map<String, dynamic>> completePasswordReset({
    String? loginId,
    String? token,
    required String newPassword,
  }) async {
    final query = <String, String>{};
    if (loginId != null && loginId.isNotEmpty) {
      query['loginId'] = loginId;
    }
    if (token != null && token.isNotEmpty) {
      query['token'] = token;
    }
    final url = Uri.parse(
      '$_baseUrl/api/v1/auth/password/reset/complete',
    ).replace(queryParameters: query);
    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'newPassword': newPassword}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      final errorBody = jsonDecode(response.body);
      throw Exception(
        errorBody['error'] ??
            tr('err.userfront.auth_proxy.password_reset_complete'),
      );
    }
  }

  static Future<void> sendSms(String phoneNumber) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/sms');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'phoneNumber': phoneNumber}),
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.sms_send',
        'Failed to send SMS: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> verifySmsCode(
    String phoneNumber,
    String code,
  ) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/verify-sms');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'phoneNumber': phoneNumber, 'code': code}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.code_verify',
        'Failed to verify the code: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> initQrLogin() async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/qr/init');
    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw _error(
        'err.userfront.auth_proxy.qr_init',
        'Failed to start QR login: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> pollQrStatus(String pendingRef) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/qr/poll');
    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'pendingRef': pendingRef}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    if (response.statusCode == 400) {
      return jsonDecode(response.body);
    }
    throw _error(
      'err.userfront.auth_proxy.qr_poll',
      'Failed to check QR status: {{error}}',
      detail: response.body,
    );
  }

  static Future<void> approveQrLogin(
    String pendingRef, {
    String? token,
    bool withCredentials = false,
  }) async {
    final url = Uri.parse(
      '$_baseUrl/api/v1/auth/qr/approve',
    ); // Mapping to ScanQRLogin on backend
    final payload = <String, dynamic>{'pendingRef': pendingRef};
    if (token != null && token.isNotEmpty) {
      payload['token'] = token;
    }

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(payload),
      withCredentials: withCredentials,
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.qr_approve',
        'Failed to approve QR login: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<bool> checkAdminAuth(String adminPassword) async {
    final url = Uri.parse('$_baseUrl/api/v1/admin/check');
    try {
      final response = await _get(
        url,
        headers: {
          'Content-Type': 'application/json',
          'X-Admin-Password': adminPassword,
        },
      );
      return response.statusCode == 200;
    } catch (_) {
      return false;
    }
  }

  static Future<void> createUser({
    required String loginId,
    required String adminPassword,
    String? email,
    String? phone,
    String? displayName,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/admin/users');

    final response = await _post(
      url,
      headers: {
        'Content-Type': 'application/json',
        'X-Admin-Password': adminPassword,
      },
      body: jsonEncode({
        'loginId': loginId,
        'email': email,
        'phone': phone,
        'displayName': displayName,
      }),
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.user_create',
        '사용자 생성 실패: {{error}}',
        detail: _responseErrorDetail(response),
      );
    }
  }

  static Future<List<dynamic>> listUsers(
    String adminPassword, {
    String? query,
  }) async {
    var uri = Uri.parse('$_baseUrl/api/v1/admin/users');
    if (query != null && query.isNotEmpty) {
      uri = uri.replace(queryParameters: {'text': query});
    }

    final response = await _get(
      uri,
      headers: {
        'Content-Type': 'application/json',
        'X-Admin-Password': adminPassword,
      },
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return data['users'] ?? [];
    } else {
      throw _error(
        'err.userfront.auth_proxy.user_list',
        'Failed to load the user list: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<void> deleteUser(String adminPassword, String loginId) async {
    final encodedId = Uri.encodeComponent(loginId);
    final url = Uri.parse('$_baseUrl/api/v1/admin/users/$encodedId');

    final response = await _delete(
      url,
      headers: {
        'Content-Type': 'application/json',
        'X-Admin-Password': adminPassword,
      },
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.user_delete',
        'Failed to delete the user: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<void> updateUserStatus(
    String adminPassword,
    String loginId,
    String status,
  ) async {
    final encodedId = Uri.encodeComponent(loginId);
    final url = Uri.parse('$_baseUrl/api/v1/admin/users/$encodedId/status');

    final response = await _patch(
      url,
      headers: {
        'Content-Type': 'application/json',
        'X-Admin-Password': adminPassword,
      },
      body: jsonEncode({'status': status}),
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.user_status_update',
        'Failed to update the user status: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<void> updateUserDetails({
    required String adminPassword,
    required String loginId,
    String? email,
    String? phone,
    String? displayName,
  }) async {
    final encodedId = Uri.encodeComponent(loginId);
    final url = Uri.parse('$_baseUrl/api/v1/admin/users/$encodedId');

    final body = <String, dynamic>{};
    if (email != null) body['email'] = email;
    if (phone != null) body['phone'] = phone;
    if (displayName != null) body['displayName'] = displayName;

    final response = await _patch(
      url,
      headers: {
        'Content-Type': 'application/json',
        'X-Admin-Password': adminPassword,
      },
      body: jsonEncode(body),
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.user_update',
        '사용자 수정 실패: {{error}}',
        detail: _responseErrorDetail(response),
      );
    }
  }

  static Future<List<dynamic>> fetchLinkedRps() async {
    final url = Uri.parse('$_baseUrl/api/v1/user/rp/linked');
    final useCookie = AuthTokenStore.usesCookie();
    final token = AuthTokenStore.getToken();

    final headers = <String, String>{'Content-Type': 'application/json'};
    if (!useCookie && token != null) {
      headers['Authorization'] = 'Bearer $token';
    }

    final response = await _get(
      url,
      headers: headers,
      withCredentials: useCookie,
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return data['items'] ?? [];
    } else {
      throw _error(
        'err.userfront.auth_proxy.linked_apps_load',
        'Failed to load linked applications.',
      );
    }
  }

  static Future<void> revokeLinkedRp(String clientId) async {
    final url = Uri.parse('$_baseUrl/api/v1/user/rp/linked/$clientId');
    final useCookie = AuthTokenStore.usesCookie();
    final token = AuthTokenStore.getToken();

    final headers = <String, String>{'Content-Type': 'application/json'};
    if (!useCookie && token != null) {
      headers['Authorization'] = 'Bearer $token';
    }

    final response = await _delete(
      url,
      headers: headers,
      withCredentials: useCookie,
    );

    if (response.statusCode != 200) {
      final errorBody = jsonDecode(response.body);
      throw Exception(
        errorBody['error'] ?? tr('err.userfront.auth_proxy.linked_app_revoke'),
      );
    }
  }

  static Future<void> sendLog(
    String level,
    String message, {
    Map<String, dynamic>? data,
  }) async {
    if (!_canSendClientLog()) {
      return;
    }
    final appEnv = envOrDefault('APP_ENV', 'dev');
    final productionDebugFlag = envOrDefault(
      'CLIENT_LOG_DEBUG',
      envOrDefault('USERFRONT_DEBUG_LOG', ''),
    );
    if (!LogPolicy.shouldRelayClientLog(
      level: level,
      appEnv: appEnv,
      productionDebugFlag: productionDebugFlag,
    )) {
      return;
    }
    final url = Uri.parse('$_baseUrl/api/v1/client-log');
    final sanitizedMessage = LogPolicy.sanitizeMessage(message);
    final sanitizedData = data == null ? null : LogPolicy.sanitizeData(data);
    try {
      await _post(
        url,
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({
          'level': level,
          'message': sanitizedMessage,
          if (sanitizedData != null) 'data': sanitizedData,
        }),
      );
      _recordClientLogSuccess();
    } catch (_) {
      _recordClientLogFailure();
      // Ignore logging errors to prevent loops
    }
  }

  static Future<void> logError(
    String message, {
    dynamic error,
    StackTrace? stackTrace,
  }) async {
    final data = <String, dynamic>{};
    if (error != null) data['error'] = error.toString();
    if (stackTrace != null) data['stack'] = stackTrace.toString();

    await sendLog('ERROR', message, data: data);
  }

  static int _clientLogFailureCount = 0;
  static DateTime? _clientLogOpenUntil;

  static bool _canSendClientLog() {
    final now = DateTime.now();
    final openUntil = _clientLogOpenUntil;
    if (openUntil != null && now.isBefore(openUntil)) {
      return false;
    }
    return true;
  }

  static void _recordClientLogFailure() {
    _clientLogFailureCount += 1;
    if (_clientLogFailureCount >= 3) {
      _clientLogOpenUntil = DateTime.now().add(const Duration(minutes: 1));
      _clientLogFailureCount = 0;
    }
  }

  static void _recordClientLogSuccess() {
    _clientLogFailureCount = 0;
    _clientLogOpenUntil = null;
  }

  // --- Signup Methods ---

  static Future<bool> checkEmailAvailability(String email) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/signup/check-email');
    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'email': email}),
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return data['available'] ?? false;
    }
    return false;
  }

  static Future<Map<String, dynamic>> checkLoginIDAvailability(
    String loginId, {
    String? tenantSlug,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/signup/check-login-id');
    final bodyData = {'loginId': loginId};
    if (tenantSlug != null && tenantSlug.isNotEmpty) {
      bodyData['tenantSlug'] = tenantSlug;
    }
    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(bodyData),
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return {
        'available': data['available'] ?? false,
        'message': data['message'],
      };
    } else {
      final data = jsonDecode(response.body);
      return {
        'available': false,
        'message': data['message'] ?? 'Failed to check ID',
      };
    }
  }

  static Future<List<Map<String, dynamic>>> getActiveTenants({
    String? email,
  }) async {
    var uriString = '$_baseUrl/api/v1/auth/signup/tenants';
    if (email != null && email.isNotEmpty) {
      uriString += '?email=${Uri.encodeComponent(email)}';
    }

    final url = Uri.parse(uriString);
    final response = await _get(url);
    if (response.statusCode == 200) {
      final List<dynamic> list = jsonDecode(response.body);
      return list.cast<Map<String, dynamic>>();
    }
    return [];
  }

  static Future<void> sendSignupCode(String target, String type) async {
    final path = type == 'email' ? 'send-email-code' : 'send-sms-code';
    final url = Uri.parse('$_baseUrl/api/v1/auth/signup/$path');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'target': target}),
    );

    if (response.statusCode != 200) {
      throw _error(
        'err.userfront.auth_proxy.phone_code_send',
        'Failed to send the verification code: {{error}}',
        detail: response.body,
      );
    }
  }

  static Future<Map<String, dynamic>> verifySignupCode(
    String target,
    String type,
    String code,
  ) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/signup/verify-code');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'target': target, 'type': type, 'code': code}),
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    throw Exception('Verification failed');
  }

  static Future<void> signup({
    required String email,
    required String password,
    required String name,
    required String phone,
    required String affiliationType,
    String? tenantSlug,
    required String department,
    required bool termsAccepted,
  }) async {
    final url = Uri.parse('$_baseUrl/api/v1/auth/signup');

    final response = await _post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'email': email,
        'password': password,
        'name': name,
        'phone': phone,
        'affiliationType': affiliationType,
        'tenantSlug': tenantSlug,
        'department': department,
        'termsAccepted': termsAccepted,
      }),
    );

    if (response.statusCode != 200) {
      String errorMessage = 'Signup failed';
      try {
        final decoded = jsonDecode(response.body);
        if (decoded is Map<String, dynamic> && decoded.containsKey('error')) {
          errorMessage = decoded['error'];
        }
      } catch (e) {
        // Fallback if the body isn't valid JSON (e.g., an HTML error page)
        errorMessage =
            'Server error (${response.statusCode}): ${response.body.isNotEmpty ? response.body.substring(0, response.body.length > 100 ? 100 : response.body.length) : "Unknown error"}';
      }
      throw Exception(errorMessage);
    }
  }
}

class AuthProxyException implements Exception {
  final String errorCode;
  final String message;
  final Map<String, dynamic>? details;

  const AuthProxyException({
    required this.errorCode,
    required this.message,
    this.details,
  });

  @override
  String toString() => message;
}
