import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:helpers/helpers/print.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:rluv/global/store.dart';

import '../models/token.dart';

final tokenProvider = StateProvider<Token?>((ref) {
  final jwt = ref.watch(jwtProvider);
  printLime('Current token: $jwt');
  if (jwt == null) return null;
  try {
    return Token.fromJson(JwtDecoder.decode(jwt));
  } catch (_) {
    return null;
  }
});

final jwtProvider = StateNotifierProvider<_JwtNotifier, String?>((ref) {
  final prefs = ref.watch(prefsProvider);
  final jwt = prefs?.getString('jwt');
  return _JwtNotifier(ref, jwt);
});

class _JwtNotifier extends StateNotifier<String?> {
  _JwtNotifier(this.ref, String? jwt) : super(null) {
    if (jwt != null) {
      setToken(jwt);
    }
  }

  final StateNotifierProviderRef ref;

  void setToken(String jwt) {
    state = jwt;
    printCyan('Loaded jwt into client: $jwt');
    ref.read(prefsProvider)?.setString('jwt', jwt);
  }

  void revokeToken() {
    printCyan('jwt token revoked');
    state = null;
    ref.read(prefsProvider)?.remove('jwt');
  }
}

final apiProvider = StateNotifierProvider<_ApiNotifier, Dio>((ref) {
  return _ApiNotifier(ref, Dio());
});

class _ApiNotifier extends StateNotifier<Dio> {
  _ApiNotifier(this.ref, this.dio) : super(dio) {
    // dio.options.baseUrl = "https://af70-136-36-2-234.ngrok-free.app/";
    // dio.options.baseUrl = "http://localhost:8081/";
    dio.options.baseUrl = "https://rluv.fosscat.com/";
    dio.interceptors.addAll([
      InterceptorsWrapper(onRequest:
          (RequestOptions options, RequestInterceptorHandler handler) {
        final jwt = ref.read(jwtProvider);
        if (jwt != null) {
          options.headers['token'] = jwt;
        }
        return handler.next(options);
      }, onResponse: (Response response, ResponseInterceptorHandler handler) {
        if (response.statusCode != null) {
          if (response.statusCode == 200) {
            try {
              if ((response.data as Map<String, dynamic>)
                  .containsKey('success')) {
                if (!response.data['success']) return handler.next(response);
              }
              if ((response.data as Map<String, dynamic>)
                  .containsKey('token')) {
                final jwt = response.data['token'];
                if (jwt != null) {
                  ref.read(jwtProvider.notifier).setToken(jwt);
                  // ref.read(tokenProvider.notifier).state =
                  //     Token.fromJson(jwtData);
                }
              }
            } catch (err) {
              printRed('Error in interceptor for token: $err');
              return handler.next(response);
            }
          }
        }
        return handler.next(response);
      }, onError: (DioException err, ErrorInterceptorHandler handler) {
        if (err.response?.statusCode == 403) {
          ref.read(jwtProvider.notifier).revokeToken();
        }
        return handler.next(err);
      }),
      _LoggingInterceptor(),
    ]);
  }

  final Dio dio;
  final StateNotifierProviderRef ref;

  Future<Map<String, dynamic>?> get(String path) async {
    try {
      final res = await dio.get(path);

      if (res.data != null) {
        return res.data as Map<String, dynamic>;
      }
      return null;
    } catch (err) {
      return null;
    }
  }

  Future<Map<String, dynamic>?> put(
      {required String path, Object? data}) async {
    try {
      final res = await dio.put(path, data: data);

      if (res.data != null) {
        return res.data as Map<String, dynamic>;
      }
      return null;
    } catch (err) {
      return null;
    }
  }

  Future<Map<String, dynamic>?> post(
      {required String path, Object? data}) async {
    try {
      final res = await dio.post(path, data: data);

      if (res.data != null) {
        return res.data as Map<String, dynamic>;
      }
      return null;
    } catch (err) {
      return null;
    }
  }

  Future<Map<String, dynamic>?> delete(
      {required String path, Object? data}) async {
    try {
      final res = await dio.delete(path, data: data);

      if (res.data != null) {
        return res.data as Map<String, dynamic>;
      }
      return null;
    } catch (err) {
      return null;
    }
  }
}

class _LoggingInterceptor extends Interceptor {
  _LoggingInterceptor();

  @override
  Future onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    logPrint('///*** REQUEST ***\\\\\\');
    printKV('URI', options.uri);
    printKV('METHOD', options.method);
    logPrint('HEADERS:');
    options.headers.forEach((key, v) => printKV(' - $key', v));
    logPrint('BODY:');
    printJson(options.data);

    return handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    printRed('///*** ERROR RESPONSE ***\\\\\\');
    logPrint('URI: ${err.requestOptions.uri}');
    if (err.response != null) {
      logPrint('STATUS CODE: ${err.response?.statusCode?.toString()}');
    }
    logPrint('$err');
    if (err.response != null) {
      printKV('REDIRECT', err.response?.realUri ?? '');
      logPrint('BODY:');
      printJson(err.response?.data);
    }
    return handler.next(err);
  }

  @override
  Future onResponse(
      Response response, ResponseInterceptorHandler handler) async {
    logPrint('///*** RESPONSE ***\\\\\\');
    printKV('URI', response.requestOptions.uri);
    printKV('STATUS CODE', response.statusCode ?? '');
    // printKV('REDIRECT', response.isRedirect);
    logPrint('BODY:');
    printJson(response.data);

    return handler.next(response);
  }

  void printKV(String key, Object v) {
    if (kDebugMode) {
      printOrange('$key: $v');
    }
  }

  void printJson(dynamic s) {
    try {
      final data = (s as Map<String, dynamic>?);
      if (kDebugMode) {
        if (data == null) {
          printAmber({});
          return;
        }
        JsonEncoder encoder = const JsonEncoder.withIndent('  ');
        String prettyprint = encoder.convert(s);
        printAmber(prettyprint);
      }
    } catch (_) {
      printAmber(s);
    }
  }

  void logPrint(String s) {
    if (kDebugMode) {
      printOrange(s);
    }
  }
}
