import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/shared/widgets/base_view/base_response_model.dart';
import '../../app/config/callbacks.dart';
import '../../app/config/constants.dart';

enum Method { GET, POST, PUT, DELETE }

class RestfulAPIClient {
  final Dio _dio;
  Json header = {};
  RestfulAPIClient(this._dio);

  Future<BaseResponseModel<T>> fetchObject<T>(String path, T Function(dynamic json) fromJsonT) =>
      requestNormal<T>(path, Method.GET, const {}, fromJsonT);

  Future<BaseResponseModel<List<T>>> fetchList<T>(String path, T Function(dynamic json) fromJsonT) =>
      requestNormal<List<T>>(path, Method.GET, const {}, (jsonRoot) {
        if (jsonRoot is List) {
          return jsonRoot.map(fromJsonT).toList();
        }
        if (jsonRoot is Map && jsonRoot['data'] is List) {
          return (jsonRoot['data'] as List).map(fromJsonT).toList();
        }
        return <T>[];
      });

  Future<BaseResponseModel<T>> requestNormal<T>(
    String path,
    Method method,
    Json params,
    T Function(dynamic json) parser, {
    bool silent = false,
    bool allowRetry = false,
  }) async {
    final isGet = method == Method.GET;
    final query = isGet ? params : const <String, dynamic>{};
    final body = isGet ? const <String, dynamic>{} : params;
    final opt = _opts(method, silent, allowRetry).compose(_dio.options, path, queryParameters: query, data: body);
    try {
      final res = await _dio.fetch<dynamic>(opt);
      final status = res.statusCode ?? 0;
      final map = _asJson(res.data);
      try {
        final model = BaseResponseModel<T>.fromJson(map, parser);
        return model;
      } catch (_) {
        final msg = _extractMessage(map, status) ?? 'HTTP $status';
        if (_isOk(status)) {
          T? data;
          try { data = parser(map); } catch (_) {}
          return BaseResponseModel<T>(status: "success", message: map['message']?.toString(), data: data, code: status);
        } else {
          return BaseResponseModel<T>(status: "fail", message: msg, data: null, code: status);
        }
      }
    } on DioException catch (e) {
      _debug('DioException: $e');
      final status = e.response?.statusCode;
      final map = _asJson(e.response?.data);
      final errorCode = map['error_code']?.toString() ?? map['errorCode']?.toString() ?? e.error?.toString();
      if (errorCode != null && ErrorCodes.tokenInvalidCodes.contains(errorCode)) {
        rethrow;
      }
      final msg = _extractMessage(map, status) ?? e.message ?? Constants.commonError;
      return BaseResponseModel<T>(status: "fail", message: msg, data: null, code: status);
    } catch (e) {
      _debug('Unknown exception: $e');
      return BaseResponseModel<T>(status: "fail", message: Constants.commonError, data: null);
    }
  }

  Future<BaseResponseModel<T>> requestMultipart<T>(
    String path,
    FormData formData,
    T Function(dynamic json) parser, {
    bool silent = false,
    bool allowRetry = false,
  }) async {
    final opt = _opts(Method.POST, silent, allowRetry)
        .copyWith(contentType: 'multipart/form-data')
        .compose(_dio.options, path, data: formData);
    try {
      final res = await _dio.fetch<dynamic>(opt);
      final status = res.statusCode ?? 0;
      final map = _asJson(res.data);
      try {
        final model = BaseResponseModel<T>.fromJson(map, parser);
        return model;
      } catch (_) {
        final msg = _extractMessage(map, status) ?? 'HTTP $status';
        if (_isOk(status)) {
          T? data;
          try {
            data = parser(map);
          } catch (_) {}
          return BaseResponseModel<T>(
            status: "success",
            message: map['message']?.toString(),
            data: data,
            code: status,
          );
        } else {
          return BaseResponseModel<T>(status: "fail", message: msg, data: null, code: status);
        }
      }
    } on DioException catch (e) {
      _debug('DioException: $e');
      final status = e.response?.statusCode;
      final map = _asJson(e.response?.data);
      final errorCode = map['error_code']?.toString() ?? map['errorCode']?.toString() ?? e.error?.toString();
      if (errorCode != null && ErrorCodes.tokenInvalidCodes.contains(errorCode)) {
        rethrow;
      }
      final msg = _extractMessage(map, status) ?? e.message ?? Constants.commonError;
      return BaseResponseModel<T>(status: "fail", message: msg, data: null, code: status);
    } catch (e) {
      _debug('Unknown exception: $e');
      return BaseResponseModel<T>(status: "fail", message: Constants.commonError, data: null);
    }
  }

  Options _opts(Method m, bool silent, bool allowRetry) => Options(
    method: m.name,
    validateStatus: (_) => true,
    receiveDataWhenStatusError: true,
    extra: {'silent': silent, 'allow_retry': allowRetry},
  );
  bool _isOk(int? code) => code != null && code >= 200 && code < 300;

  /// Ép mọi kiểu body về Map:
  /// - Map: trả nguyên
  /// - String: cố gắng jsonDecode → Map; nếu không, bọc thành {'message': '...'}
  /// - List: bọc thành {'data': List}
  /// - null/khác: {'message': '...'} hoặc message chung
  Json _asJson(dynamic data) {
    if (data is Json) return data;
    if (data is String) {
      try {
        final decoded = jsonDecode(data);
        if (decoded is Json) return decoded;
        if (decoded is List) return <String, dynamic>{'data': decoded};
        return <String, dynamic>{'message': data};
      } catch (_) {
        return <String, dynamic>{'message': data};
      }
    }
    if (data is List) {
      return <String, dynamic>{'data': data};
    }
    if (data == null) {
      return <String, dynamic>{'message': Constants.commonError};
    }
    return <String, dynamic>{'message': data.toString()};
  }
  /// Rút message từ nhiều key phổ biến; fallback HTTP code
  String? _extractMessage(Json j, [int? status]) {
    const keys = ['message', 'errorMessage', 'error_message', 'error', 'msg', 'detail', 'description'];
    for (final k in keys) {
      final v = j[k];
      if (v != null) {
        final s = v.toString().trim();
        if (s.isNotEmpty) return s;
      }
    }
    return status == null ? null : 'HTTP $status';
  }

  void _debug(Object e) {
    debugPrint('=== API DEBUG === $e');
  }
}
