import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:http_interceptor/http_interceptor.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import '../models/user_profile.dart';
import '../utils/logging_interceptor.dart';
import '../utils/http_client_factory.dart';

class AuthConfig {
  final String realm;
  final String authServerUrl;
  final String clientId;
  final bool allowSelfSignedCertificate;

  const AuthConfig({
    required this.realm,
    required this.authServerUrl,
    required this.clientId,
    this.allowSelfSignedCertificate = false,
  });

  static const AuthConfig development = AuthConfig(
    realm: 'dev',
    authServerUrl: 'https://auth.goutezplanb.com',
    clientId: 'delivery-mobile-app',
    allowSelfSignedCertificate: true,
  );

  static const AuthConfig production = AuthConfig(
    realm: 'planb-internal',
    authServerUrl: 'https://auth.goutezplanb.com',
    clientId: 'delivery-mobile-app',
  );

  String get tokenEndpoint => '$authServerUrl/realms/$realm/protocol/openid-connect/token';
}

class AuthService {
  static const String _tokenKey = 'auth_token';
  static const String _refreshTokenKey = 'refresh_token';

  final AuthConfig _config;
  final FlutterSecureStorage _secureStorage;
  final http.Client _httpClient;

  AuthService({
    AuthConfig config = AuthConfig.development,
    FlutterSecureStorage? secureStorage,
    http.Client? httpClient,
  })  : _config = config,
        _secureStorage = secureStorage ?? const FlutterSecureStorage(
          aOptions: AndroidOptions(
            encryptedSharedPreferences: true,
          ),
          iOptions: IOSOptions(
            accessibility: KeychainAccessibility.first_unlock,
          ),
          mOptions: MacOsOptions(
            accessibility: KeychainAccessibility.first_unlock,
          ),
        ),
        _httpClient = httpClient ?? InterceptedClient.build(
          interceptors: [LoggingInterceptor()],
          client: HttpClientFactory.createClient(
            allowSelfSigned: config.allowSelfSignedCertificate,
          ),
        );

  Future<AuthResult> login({
    required String username,
    required String password,
  }) async {
    try {
      final response = await _httpClient.post(
        Uri.parse(_config.tokenEndpoint),
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: {
          'grant_type': 'password',
          'client_id': _config.clientId,
          'username': username,
          'password': password,
          'scope': 'openid profile offline_access',
        },
      );

      if (response.statusCode == 200) {
        final data = json.decode(response.body) as Map<String, dynamic>;
        final accessToken = data['access_token'] as String;
        final refreshToken = data['refresh_token'] as String?;

        await _secureStorage.write(key: _tokenKey, value: accessToken);
        if (refreshToken != null) {
          await _secureStorage.write(key: _refreshTokenKey, value: refreshToken);
        }

        return AuthResult.success(token: accessToken);
      } else if (response.statusCode == 401) {
        return AuthResult.error(error: 'Invalid username or password');
      } else {
        return AuthResult.error(error: 'Authentication failed: ${response.statusCode}');
      }
    } catch (e) {
      return AuthResult.error(error: 'Network error: ${e.toString()}');
    }
  }

  Future<AuthResult> refreshAccessToken() async {
    try {
      final refreshToken = await getRefreshToken();
      if (refreshToken == null) {
        return AuthResult.error(error: 'No refresh token available');
      }

      final response = await _httpClient.post(
        Uri.parse(_config.tokenEndpoint),
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: {
          'grant_type': 'refresh_token',
          'client_id': _config.clientId,
          'refresh_token': refreshToken,
        },
      );

      if (response.statusCode == 200) {
        final data = json.decode(response.body) as Map<String, dynamic>;
        final accessToken = data['access_token'] as String;
        final newRefreshToken = data['refresh_token'] as String?;

        await _secureStorage.write(key: _tokenKey, value: accessToken);
        if (newRefreshToken != null) {
          await _secureStorage.write(key: _refreshTokenKey, value: newRefreshToken);
        }

        return AuthResult.success(token: accessToken);
      } else {
        await logout();
        return AuthResult.error(error: 'Token refresh failed');
      }
    } catch (e) {
      return AuthResult.error(error: 'Token refresh error: ${e.toString()}');
    }
  }

  Future<void> logout() async {
    await Future.wait([
      _secureStorage.delete(key: _tokenKey),
      _secureStorage.delete(key: _refreshTokenKey),
    ]);
  }

  Future<String?> getToken() async {
    return await _secureStorage.read(key: _tokenKey);
  }

  Future<String?> getRefreshToken() async {
    return await _secureStorage.read(key: _refreshTokenKey);
  }

  bool isTokenValid(String? token) {
    if (token == null || token.isEmpty) return false;
    try {
      return !JwtDecoder.isExpired(token);
    } catch (e) {
      return false;
    }
  }

  /// Check if token expires within the specified duration (default: 5 minutes)
  bool isTokenExpiringSoon(String? token, {Duration threshold = const Duration(minutes: 5)}) {
    if (token == null || token.isEmpty) return true;
    try {
      final decodedToken = JwtDecoder.decode(token);
      final exp = decodedToken['exp'] as int?;
      if (exp == null) return true;

      final expirationTime = DateTime.fromMillisecondsSinceEpoch(exp * 1000);
      final now = DateTime.now();
      final timeUntilExpiration = expirationTime.difference(now);

      return timeUntilExpiration <= threshold;
    } catch (e) {
      return true;
    }
  }

  /// Proactively refresh token if it's expiring soon
  /// Returns the current valid token or a newly refreshed token
  Future<String?> ensureValidToken() async {
    final currentToken = await getToken();

    // If no token, return null
    if (currentToken == null) return null;

    // If token is still valid and not expiring soon, return it
    if (!isTokenExpiringSoon(currentToken)) {
      return currentToken;
    }

    // Token is expiring soon, refresh it
    final refreshResult = await refreshAccessToken();
    return refreshResult.when(
      success: (newToken) => newToken,
      onError: (error) => null,
      cancelled: () => null,
    );
  }

  UserProfile? decodeToken(String token) {
    try {
      final decodedToken = JwtDecoder.decode(token);
      return UserProfile.fromJwtClaims(decodedToken);
    } catch (e) {
      return null;
    }
  }

  Future<bool> isAuthenticated() async {
    final token = await getToken();
    return isTokenValid(token);
  }
}

sealed class AuthResult {
  const AuthResult();

  factory AuthResult.success({required String token}) => _Success(token);
  factory AuthResult.error({required String error}) => _Error(error);
  const factory AuthResult.cancelled() = _Cancelled;

  R when<R>({
    required R Function(String token) success,
    required R Function(String error) onError,
    required R Function() cancelled,
  }) {
    return switch (this) {
      _Success(:final token) => success(token),
      _Error(:final error) => onError(error),
      _Cancelled() => cancelled(),
    };
  }
}

final class _Success extends AuthResult {
  final String token;
  const _Success(this.token);
}

final class _Error extends AuthResult {
  final String error;
  const _Error(this.error);
}

final class _Cancelled extends AuthResult {
  const _Cancelled();
}
