import 'dart:io';

import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tm_app/data/services/model/login_response/login_response_model.dart';

import '../config/app_config.dart';
import '../utils/logger.dart';
import '../utils/result.dart';
import 'app_exceptions.dart';
import 'network_connectivity.dart';

class NetworkManager {
  final Dio mainDio;
  final NetworkConnectivity _connectivity = NetworkConnectivity();
  bool _interceptorsAdded = false;
  final SharedPreferences _prefs;

  NetworkManager(this._prefs)
    : mainDio = Dio(
        BaseOptions(
          baseUrl: AppConfig.apiBaseUrl,
          connectTimeout: const Duration(seconds: 10),
          receiveTimeout: const Duration(seconds: 10),
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
          },
        ),
      ) {
    _addInterceptors();
  }

  void _addInterceptors() {
    if (_interceptorsAdded) {
      return;
    }

    final requestInterceptor = InterceptorsWrapper(
      onRequest: (options, handler) {
        appLogger.info('🌐 Request: ${options.method} ${options.path}');
        String? token = _prefs.getString('bearer');
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        return handler.next(options);
      },
      onResponse: (response, handler) {
        appLogger.info(
          '✅ Response: ${response.statusCode} ${response.requestOptions.path}',
        );

        return handler.next(response);
      },
      onError: (error, handler) async {
        appLogger.error(
          '❌ Error: ${error.message} - ${error.requestOptions.path}',
        );
        appLogger.error('❌ Error type: ${error.type}');

        if (error.response?.statusCode == 401) {
          // Skip refresh token for login and refresh token endpoints
          final path = error.requestOptions.path;
          if (path.contains('/api/v1/profile/login') ||
              path.contains('/api/v1/profile/refresh-token')) {
            appLogger.info(
              '🔄 Skipping token refresh for authentication endpoints',
            );
            return handler.next(error);
          }

          final newAccessToken = await refreshToken();
          if (newAccessToken != null) {
            mainDio.options.headers['Authorization'] = 'Bearer $newAccessToken';
            return handler.resolve(await mainDio.fetch(error.requestOptions));
          }
        }
        return handler.next(error);
      },
    );

    mainDio.interceptors.add(requestInterceptor);
    _interceptorsAdded = true;
  }

  /// Enhanced makeRequest method with comprehensive error handling
  Future<Result<T>> makeRequest<T>(
    String endpoint,
    T Function(Map<String, dynamic>) fromJson, {
    String method = 'GET',
    Object? data,
    Map<String, dynamic>? queryParameters,
  }) async {
    appLogger.info('🚀 network request: $method $endpoint');

    try {
      // Check network connectivity first
      if (!await _connectivity.hasInternetConnection()) {
        appLogger.error('❌ No internet connection available');
        return Result.error(
          NetworkConnectionException('No internet connection available'),
        );
      }

      Dio dioInstance = mainDio;

      Future<Response> makeApiCall(String token) async {
        final options = Options(
          validateStatus:
              (status) => status != null && status >= 200 && status < 300,
        );

        switch (method.toUpperCase()) {
          case 'POST':
            return await dioInstance.post(
              endpoint,
              data: data,
              queryParameters: queryParameters,
              options: options,
            );
          case 'PUT':
            return await dioInstance.put(
              endpoint,
              data: data,
              queryParameters: queryParameters,
              options: options,
            );
          case 'PATCH':
            return await dioInstance.patch(
              endpoint,
              data: data,
              queryParameters: queryParameters,
              options: options,
            );
          case 'DELETE':
            return await dioInstance.delete(
              endpoint,
              data: data,
              queryParameters: queryParameters,
              options: options,
            );
          case 'GET':
            return await dioInstance.get(
              endpoint,
              queryParameters: queryParameters,
              options: options,
            );
          default:
            return await dioInstance.get(
              endpoint,
              queryParameters: queryParameters,
              options: options,
            );
        }
      }

      String? bearerToken = _prefs.getString('bearer');
      // String? url = prefs.getString('url');

      // appLogger.info('🔗 Base URL: ${url ?? 'NOT SET'}');
      appLogger.info(
        '🔑 Bearer token: ${bearerToken != null ? 'PRESENT' : 'NOT SET'}',
      );

      // if (url == null || url.isEmpty) {
      //   appLogger.error('❌ Base URL not configured');
      //   return Result.error(BadRequestException('Base URL not configured'));
      // }

      Response res = await makeApiCall(bearerToken ?? '');

      // Handle successful responses
      if (res.statusCode == 200 || res.statusCode == 201) {
        appLogger.info('✅ Request successful: ${res.statusCode}');
        try {
          final result = fromJson(res.data);
          appLogger.info('✅ Response parsed successfully');
          return Result.ok(result);
        } catch (e) {
          appLogger.error('❌ Failed to parse response: $e');
          return Result.error(
            FetchDataException(
              'Failed to parse response data',
              res.statusCode,
              e,
            ),
          );
        }
      }

      // Handle specific HTTP error codes
      return _handleHttpError(res);
    } on DioException catch (e) {
      return _handleDioException(e);
    } on Exception catch (e) {
      appLogger.error('Unexpected error in makeRequest: $e');
      return Result.error(
        FetchDataException('Unexpected error occurred', null, e),
      );
    }
  }

  /// Handle HTTP error responses
  Result<T> _handleHttpError<T>(Response response) {
    final statusCode = response.statusCode;
    final message = _extractErrorMessage(response.data);

    switch (statusCode) {
      case 400:
        return Result.error(
          BadRequestException(message, statusCode, response.data),
        );
      case 401:
        return Result.error(
          AuthenticationException(message, statusCode, response.data),
        );
      case 403:
        return Result.error(
          ForbiddenException(message, statusCode, response.data),
        );
      case 404:
        return Result.error(
          NotFoundException(message, statusCode, response.data),
        );
      case 423:
        return Result.error(
          ForbiddenException(
            'Resource is locked: $message',
            statusCode,
            response.data,
          ),
        );
      case 429:
        return Result.error(
          RateLimitException(message, statusCode, response.data),
        );
      case 500:
      case 502:
      case 503:
      case 504:
        return Result.error(
          ServerException(message, statusCode, response.data),
        );
      default:
        return Result.error(
          FetchDataException(message, statusCode, response.data),
        );
    }
  }

  /// Handle Dio exceptions
  Result<T> _handleDioException<T>(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return Result.error(TimeoutException('Request timed out', e));

      case DioExceptionType.connectionError:
        return Result.error(
          NetworkConnectionException('Connection error occurred', e),
        );

      case DioExceptionType.badResponse:
        if (e.response?.statusCode == 401) {
          // Handle token refresh logic here if needed
          // For now, return authentication error
          return Result.error(
            AuthenticationException(
              'Authentication failed',
              e.response?.statusCode,
              e,
            ),
          );
        }
        return Result.error(
          FetchDataException(
            'Bad response from server',
            e.response?.statusCode,
            e,
          ),
        );

      case DioExceptionType.cancel:
        return Result.error(
          FetchDataException('Request was cancelled', null, e),
        );

      case DioExceptionType.unknown:
      default:
        if (e.error is SocketException) {
          return Result.error(
            NetworkConnectionException('No internet connection', e),
          );
        }
        return Result.error(
          FetchDataException('Network error occurred', null, e),
        );
    }
  }

  /// Extract error message from response data
  String _extractErrorMessage(data) {
    if (data == null) {
      return 'Unknown error';
    }

    if (data is Map<String, dynamic>) {
      return data['message'] ??
          data['error'] ??
          data['detail'] ??
          'Unknown error';
    }

    if (data is String) {
      return data;
    }

    return 'Unknown error';
  }

  /// Refresh the access token using the refresh token
  Future<String?> refreshToken() async {
    try {
      final refreshToken = _prefs.getString('refresh_token');
      if (refreshToken == null) {
        appLogger.error('❌ No refresh token available');
        return null;
      }

      final result = await makeRequest<LoginResponseModel>(
        '/api/v1/profile/refresh-token',
        (json) => LoginResponseModel.fromJson(json),
        method: 'POST',
        data: {'refresh_token': refreshToken},
      );

      if (result is Ok<LoginResponseModel>) {
        final loginResponse = result.value;
        final newAccessToken = loginResponse.data.accessToken;
        final newRefreshToken = loginResponse.data.refreshToken;

        if (newAccessToken != null) {
          await _prefs.setString('bearer', newAccessToken);
          if (newRefreshToken != null) {
            await _prefs.setString('refresh_token', newRefreshToken);
          }
          appLogger.info('✅ Token refreshed successfully');
          return newAccessToken;
        }
      } else if (result is Error<LoginResponseModel>) {
        appLogger.error('❌ Token refresh failed: ${result.error}');
      }
    } catch (e) {
      appLogger.error('❌ Token refresh error: $e');
    }
    return null;
  }
}
