import 'package:dio/dio.dart';

import '../../../model/account_data.dart';
import '../queries/auth_login/auth_login.dart';
import 'device_token_name.dart';
import 'token_storage.dart';

/// Adds the bearer token to outgoing Marianum-Connect requests and, on 401,
/// re-logs in once with the credentials in [AccountData] before retrying.
class MarianumConnectAuthInterceptor extends Interceptor {
  static const _retriedKey = 'mc_auth_retried';

  final MarianumConnectTokenStorage _tokenStorage;
  final Dio _retryDio;
  final AuthLogin _loginClient;

  // Single-flight lock: parallel 401s share the same login Future instead of
  // each spawning a fresh row in api_tokens.
  Future<bool>? _pendingReLogin;

  MarianumConnectAuthInterceptor({
    MarianumConnectTokenStorage tokenStorage =
        const MarianumConnectTokenStorage(),
    Dio? retryDio,
    AuthLogin? loginClient,
  }) : _tokenStorage = tokenStorage,
       _retryDio = retryDio ?? Dio(),
       _loginClient = loginClient ?? AuthLogin();

  @override
  Future<void> onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    // Wait for an in-flight re-login so nachrückende Requests den frischen
    // Token mitschicken statt ein eigenes 401 einzufangen.
    final pending = _pendingReLogin;
    if (pending != null) await pending;
    // Reading the keystore can throw while the device is locked (iOS
    // errSecInteractionNotAllowed on background requests). Degrade to an
    // unauthenticated request instead of surfacing a platform error.
    String? token;
    try {
      token = await _tokenStorage.readToken();
    } catch (_) {
      token = null;
    }
    if (token != null && token.isNotEmpty) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  }

  @override
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    final response = err.response;
    if (response?.statusCode != 401 ||
        err.requestOptions.extra[_retriedKey] == true) {
      handler.next(err);
      return;
    }
    final refreshed = await _attemptReLogin();
    if (!refreshed) {
      handler.next(err);
      return;
    }
    try {
      final retried = await _retryWithFreshToken(err.requestOptions);
      handler.resolve(retried);
    } on DioException catch (retryError) {
      handler.next(retryError);
    }
  }

  Future<bool> _attemptReLogin() {
    final inFlight = _pendingReLogin;
    if (inFlight != null) return inFlight;
    final fresh = _performReLogin();
    _pendingReLogin = fresh;
    fresh.whenComplete(() {
      if (identical(_pendingReLogin, fresh)) _pendingReLogin = null;
    });
    return fresh;
  }

  Future<bool> _performReLogin() async {
    if (!AccountData().isPopulated()) return false;
    try {
      await _loginClient.run(
        username: AccountData().getUsername(),
        password: AccountData().getPassword(),
        tokenName: await DeviceTokenName.resolve(),
      );
      return true;
    } catch (_) {
      await _tokenStorage.clear();
      return false;
    }
  }

  Future<Response<dynamic>> _retryWithFreshToken(
    RequestOptions originalOptions,
  ) async {
    final freshToken = await _tokenStorage.readToken();
    final headers = Map<String, dynamic>.of(originalOptions.headers);
    if (freshToken != null && freshToken.isNotEmpty) {
      headers['Authorization'] = 'Bearer $freshToken';
    }
    final clone = originalOptions.copyWith(
      headers: headers,
      extra: {...originalOptions.extra, _retriedKey: true},
    );
    return _retryDio.fetch<dynamic>(clone);
  }
}
