import 'package:logging/logging.dart';

import 'package:pshared/api/errors/authorization_failed.dart';
import 'package:pshared/api/errors/unauthorized.dart';
import 'package:pshared/api/requests/tokens/access_refresh.dart';
import 'package:pshared/api/requests/tokens/refresh_rotate.dart';
import 'package:pshared/api/responses/account.dart';
import 'package:pshared/api/responses/error/server.dart';
import 'package:pshared/api/responses/login.dart';
import 'package:pshared/api/responses/token.dart';
import 'package:pshared/config/constants.dart';
import 'package:pshared/service/authorization/circuit_breaker.dart';
import 'package:pshared/service/authorization/retry_helper.dart';
import 'package:pshared/service/authorization/storage.dart';
import 'package:pshared/service/authorization/token_mutex.dart';
import 'package:pshared/service/device_id.dart';
import 'package:pshared/service/services.dart';
import 'package:pshared/utils/exception.dart';
import 'package:pshared/utils/http/requests.dart';



class TokenService {
  static final _logger = Logger('service.authorization.token');
  static const String _objectType = Services.account;

  static bool _isTokenExpiringSoon(TokenData token, Duration duration) {
    return token.expiration.isBefore(DateTime.now().add(duration));
  }

  static Future<String> getAccessToken() async {
    TokenData token = await AuthorizationStorage.getAccessToken();
    if (_isTokenExpiringSoon(token, const Duration(hours: 4))) {
      // Use mutex to prevent concurrent refresh operations
      final refreshedToken = await TokenRefreshMutex().executeRefresh(() async {
        return (await refreshAccessToken()).accessToken.token;
      });
      return refreshedToken;
    }
    return token.token;
  }

  static Future<void> _updateTokens(LoginResponse response) async {
    await AuthorizationStorage.updateToken(response.accessToken);
    await AuthorizationStorage.updateRefreshToken(response.refreshToken);
  }

  static Future<AccountResponse> refreshAccessToken() async {
    _logger.fine('Refreshing access token...');
    final deviceId = await DeviceIdManager.getDeviceId();
    final refresh = await AuthorizationStorage.getRefreshToken();

    if (_isTokenExpiringSoon(refresh, const Duration(days: 7))) {
      return await _rotateRefreshToken();
    }

    final response = await getPOSTResponse(
      _objectType, 
      '/refresh',
      AccessTokenRefreshRequest(
        deviceId: deviceId,
        clientId: Constants.clientId,
        token: refresh.token,
      ).toJson(),
    );

    final accountResp = AccountResponse.fromJson(response);
    await AuthorizationStorage.updateToken(accountResp.accessToken);
    return accountResp;
  }

  static Future<LoginResponse> _rotateRefreshToken() async {
    _logger.fine('Rotating refresh token...');
    final refresh = await AuthorizationStorage.getRefreshToken();

    if (refresh.expiration.isBefore(DateTime.now())) throw ErrorUnauthorized();

    final deviceId = await DeviceIdManager.getDeviceId();
    final response = await getPOSTResponse(
      _objectType, 
      '/rotate',
      RotateRefreshTokenRequest(
        token: refresh.token,
        clientId: Constants.clientId,
        deviceId: deviceId,
      ).toJson(),
    );

    final loginResponse = LoginResponse.fromJson(response);
    await _updateTokens(loginResponse);
    return loginResponse;
  }

  /// Enhanced method to handle unexpected 401 errors with fallback logic
  static Future<void> handleUnauthorized() async {
    _logger.warning('Handling unexpected 401 unauthorized error');
    
    return AuthCircuitBreaker.execute(() async {
      return RetryHelper.withExponentialBackoff(
        () async {
          try {
            // Try refresh first (faster)
            final currentRefresh = await AuthorizationStorage.getRefreshToken();
            if (!_isTokenExpiringSoon(currentRefresh, const Duration(days: 1))) {
              _logger.fine('Attempting access token refresh for 401 recovery');
              await TokenRefreshMutex().executeRefresh(() async {
                await refreshAccessToken();
                return 'refreshed';
              });
              return;
            }
            
            // Fallback to rotation if refresh token expiring soon
            _logger.fine('Attempting refresh token rotation for 401 recovery');
            await TokenRefreshMutex().executeRotation(() async {
              await _rotateRefreshToken();
            });
          } catch (e) {
            _logger.severe('Token recovery failed: $e');
            throw AuthenticationFailedException('Token recovery failed', toException(e));
          }
        },
        maxRetries: 2,
        shouldRetry: (error) {
          // Only retry on network errors, not auth errors
          return RetryHelper.isRetryableError(error) && !_isAuthError(error);
        },
      );
    });
  }

  /// Enhanced getAccessToken with better error handling
  static Future<String> getAccessTokenSafe() async {
    return AuthCircuitBreaker.execute(() async {
      return RetryHelper.withExponentialBackoff(
        () async {
          TokenData token = await AuthorizationStorage.getAccessToken();
          if (_isTokenExpiringSoon(token, const Duration(hours: 4))) {
            // Use mutex to prevent concurrent refresh operations
            final refreshedToken = await TokenRefreshMutex().executeRefresh(() async {
              return (await refreshAccessToken()).accessToken.token;
            });
            return refreshedToken;
          }
          return token.token;
        },
        maxRetries: 2,
        shouldRetry: (error) => RetryHelper.isRetryableError(error),
      );
    });
  }

  /// Check if error is authentication-related (non-retryable)
  static bool _isAuthError(Exception error) {
    if (error is ErrorUnauthorized || error is AuthenticationFailedException) {
      return true;
    }
    
    if (error is ErrorResponse && error.code == 401) {
      return true;
    }
    
    final errorString = error.toString().toLowerCase();
    return errorString.contains('unauthorized') || 
           errorString.contains('401') ||
           errorString.contains('authentication') ||
           errorString.contains('token');
  }

  /// Get circuit breaker status for debugging
  static Map<String, dynamic> getAuthStatus() {
    return {
      'circuitBreaker': AuthCircuitBreaker.getStatus(),
      'tokenMutex': TokenRefreshMutex().getStatus(),
      'timestamp': DateTime.now().toIso8601String(),
    };
  }

}
