import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';

import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/core/errors/failures.dart';
import 'package:superport/data/datasources/remote/interceptors/auth_interceptor.dart';
import 'package:superport/data/models/auth/refresh_token_request.dart';
import 'package:superport/data/models/auth/login_request.dart';
import 'package:superport/data/models/auth/login_response.dart';
import 'package:superport/data/models/auth/auth_user.dart';
import 'package:superport/data/models/auth/token_response.dart';
import 'package:superport/domain/repositories/auth_repository.dart';

class _InMemoryAuthRepository implements AuthRepository {
  String? _accessToken;
  String? _refreshToken;
  bool cleared = false;

  _InMemoryAuthRepository({String? accessToken, String? refreshToken})
      : _accessToken = accessToken,
        _refreshToken = refreshToken;

  @override
  Future<Either<Failure, String?>> getStoredAccessToken() async => Right(_accessToken);

  @override
  Future<Either<Failure, String?>> getStoredRefreshToken() async => Right(_refreshToken);

  @override
  Future<Either<Failure, TokenResponse>> refreshToken(RefreshTokenRequest refreshRequest) async {
    if (refreshRequest.refreshToken != _refreshToken || _refreshToken == null) {
      return Left(ServerFailure(message: 'Invalid refresh token'));
    }
    // Issue new tokens
    _accessToken = 'NEW_TOKEN';
    _refreshToken = 'NEW_REFRESH';
    return Right(TokenResponse(
      accessToken: _accessToken!,
      refreshToken: _refreshToken!,
      tokenType: 'Bearer',
      expiresIn: 3600,
    ));
  }

  @override
  Future<Either<Failure, void>> clearLocalSession() async {
    cleared = true;
    _accessToken = null;
    _refreshToken = null;
    return const Right(null);
  }

  // Unused in these tests
  @override
  Future<Either<Failure, void>> changePassword(String currentPassword, String newPassword) async =>
      const Right(null);
  @override
  Future<Either<Failure, void>> requestPasswordReset(String email) async => const Right(null);
  @override
  Future<Either<Failure, bool>> isAuthenticated() async => const Right(true);
  @override
  Future<Either<Failure, void>> logout() async => const Right(null);
  @override
  Future<Either<Failure, bool>> validateSession() async => const Right(true);
  @override
  Future<Either<Failure, LoginResponse>> login(LoginRequest loginRequest) async =>
      Left(ServerFailure(message: 'not used in test'));
  @override
  Future<Either<Failure, AuthUser>> getCurrentUser() async =>
      Left(ServerFailure(message: 'not used in test'));
}

/// Interceptor that terminates requests without hitting network.
class _TerminatorInterceptor extends Interceptor {
  RequestOptions? lastOptions;

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    lastOptions = options;
    // Default success response (200)
    handler.resolve(Response(requestOptions: options, statusCode: 200, data: {'ok': true}));
  }
}

/// Interceptor that rejects with 401 unless it sees the expected token.
class _UnauthorizedThenOkInterceptor extends Interceptor {
  final String requiredBearer;
  _UnauthorizedThenOkInterceptor(this.requiredBearer);

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final auth = options.headers['Authorization'];
    // Debug: record current auth header on the test interceptor
    // ignore: avoid_print
    print('Test UnauthorizedThenOkInterceptor saw Authorization: ' + (auth?.toString() ?? 'NULL'));
    if (auth == 'Bearer $requiredBearer') {
      handler.resolve(Response(requestOptions: options, statusCode: 200, data: {'ok': true}));
    } else {
      handler.reject(DioException(
        requestOptions: options,
        response: Response(requestOptions: options, statusCode: 401),
        type: DioExceptionType.badResponse,
        message: 'Unauthorized',
      ));
    }
  }
}

/// Simple HTTP adapter that returns 200 when Authorization matches,
/// otherwise returns 401. This is used to exercise `dio.fetch(...)` retry path.
class _AuthTestAdapter implements HttpClientAdapter {
  @override
  void close({bool force = false}) {}

  @override
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<List<int>>? requestStream,
    Future? cancelFuture,
  ) async {
    final auth = options.headers['Authorization'];
    // Login endpoint always returns 200
    if (options.path == ApiEndpoints.login) {
      return ResponseBody.fromString('{"ok":true}', 200, headers: {});
    }
    if (auth == 'Bearer NEW_TOKEN') {
      return ResponseBody.fromString('{"ok":true}', 200, headers: {});
    }
    return ResponseBody.fromString('', 401, headers: {});
  }
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  group('AuthInterceptor', () {
    late GetIt sl;

    setUp(() {
      sl = GetIt.instance;
      sl.reset(dispose: true);
    });

    tearDown(() {
      sl.reset(dispose: true);
    });

    test('attaches Authorization header for protected endpoints', () async {
      final repo = _InMemoryAuthRepository(accessToken: 'OLD_TOKEN', refreshToken: 'REFRESH');
      sl.registerLazySingleton<AuthRepository>(() => repo);

      final dio = Dio(BaseOptions(baseUrl: 'https://example.com'));
      final terminator = _TerminatorInterceptor();
      dio.interceptors.add(AuthInterceptor(dio, overrideAuthRepository: repo));
      dio.interceptors.add(terminator);

      final res = await dio.get(ApiEndpoints.users);
      expect(res.statusCode, 200);
      expect(terminator.lastOptions, isNotNull);
      expect(terminator.lastOptions!.headers['Authorization'], 'Bearer OLD_TOKEN');
    });

    test('skips Authorization header for auth endpoints', () async {
      final repo = _InMemoryAuthRepository(accessToken: 'OLD_TOKEN', refreshToken: 'REFRESH');
      sl.registerLazySingleton<AuthRepository>(() => repo);

      final dio = Dio(BaseOptions(baseUrl: 'https://example.com'));
      final terminator = _TerminatorInterceptor();
      dio.interceptors.add(AuthInterceptor(dio, overrideAuthRepository: repo));
      dio.interceptors.add(terminator);

      final res = await dio.get(ApiEndpoints.login);
      expect(res.statusCode, 200);
      expect(terminator.lastOptions, isNotNull);
      expect(terminator.lastOptions!.headers.containsKey('Authorization'), isFalse);
    });

    test('on 401, refresh token and retry succeeds', () async {
      final repo = _InMemoryAuthRepository(accessToken: 'OLD_TOKEN', refreshToken: 'REFRESH');
      sl.registerLazySingleton<AuthRepository>(() => repo);

      final dio = Dio(BaseOptions(baseUrl: 'https://example.com'));
      dio.httpClientAdapter = _AuthTestAdapter();
      dio.interceptors.add(AuthInterceptor(dio, overrideAuthRepository: repo));

      final res = await dio.get(ApiEndpoints.users);
      expect(res.statusCode, 200);
      // ensure repo now holds new token
      final tokenEither = await repo.getStoredAccessToken();
      expect(tokenEither.getOrElse(() => null), 'NEW_TOKEN');
    });

    test('on 401 and refresh fails, session cleared and error bubbles', () async {
      // Repo with no refresh token (will fail refresh)
      final repo = _InMemoryAuthRepository(accessToken: 'OLD_TOKEN', refreshToken: null);
      sl.registerLazySingleton<AuthRepository>(() => repo);
      // Verify registration exists
      expect(GetIt.instance.isRegistered<AuthRepository>(), true);

      final dio = Dio(BaseOptions(baseUrl: 'https://example.com'));
      dio.httpClientAdapter = _AuthTestAdapter();
      dio.interceptors.add(AuthInterceptor(dio, overrideAuthRepository: repo));

      DioException? caught;
      try {
        await dio.get(ApiEndpoints.users);
      } on DioException catch (e) {
        caught = e;
      }
      expect(caught, isNotNull);
      expect(caught!.response?.statusCode, 401);
      expect(repo.cleared, isTrue);
    });
  });
}
