import 'dart:convert';

import 'package:dio/dio.dart';

import '../../../app/constants/app_constants.dart';
import '../../../core/network/api_exception.dart';
import '../../../core/storage/token_storage.dart';
import '../domain/auth_repository.dart';
import '../../connection/domain/binding_device.dart';

/// 基于 Dio 的 [AuthRepository] 实现。
class DioAuthRepository implements AuthRepository {
  DioAuthRepository({Dio? dio, TokenStorage? storage})
      : _dio = dio ?? Dio(_baseOptions),
        _storage = storage ?? const TokenStorage();

  static final BaseOptions _baseOptions = BaseOptions(
    baseUrl: kApiBase,
    connectTimeout: const Duration(seconds: 15),
    receiveTimeout: const Duration(seconds: 15),
    headers: {'Content-Type': 'application/json'},
  );

  final Dio _dio;
  final TokenStorage _storage;

  // 刷新单飞：避免并发触发多次刷新。
  Future<Map<String, dynamic>>? _refreshing;

  String? _accessToken;

  @override
  String? get accessToken => _accessToken;

  @override
  Future<void> restore() async {
    _accessToken = await _storage.readAccess();
  }

  @override
  Future<bool> hasRefreshToken() async =>
      await _storage.readRefresh() != null;

  @override
  Future<String?> getUsername() => _storage.readUsername();

  Future<void> _saveTokens({
    required String accessToken,
    String? refreshToken,
    String? username,
  }) async {
    _accessToken = accessToken;
    await _storage.writeAccess(accessToken);
    if (refreshToken != null) {
      await _storage.writeRefresh(refreshToken);
    }
    if (username != null) {
      await _storage.writeUsername(username);
    }
  }

  @override
  Future<Map<String, dynamic>> login(String username, String password) async {
    final data = await _post(
      '/api/auth/login',
      body: {'username': username, 'password': password},
    );
    await _saveTokens(
      accessToken: data['accessToken'] as String,
      refreshToken: data['refreshToken'] as String?,
      username: username,
    );
    return data;
  }

  @override
  Future<Map<String, dynamic>> register(String username, String password) =>
      _post('/api/auth/register',
          body: {'username': username, 'password': password});

  @override
  Future<Map<String, dynamic>> refresh() async {
    final rt = await _storage.readRefresh();
    if (rt == null) {
      await clear();
      throw const ApiException('NO_REFRESH_TOKEN', 401);
    }
    _refreshing ??= _doRefresh(rt).whenComplete(() => _refreshing = null);
    return _refreshing!;
  }

  Future<Map<String, dynamic>> _doRefresh(String rt) async {
    final data = await _post('/api/auth/refresh', body: {'refreshToken': rt});
    await _saveTokens(
      accessToken: data['accessToken'] as String,
      refreshToken: data['refreshToken'] as String?,
    );
    return data;
  }

  @override
  Future<Map<String, dynamic>> verify() => _get('/api/client/verify');

  @override
  Future<List<BindingDevice>> getBindings() async {
    final data = await _get('/api/client/bindings');
    final list = (data['bindings'] as List?) ?? [];
    return [
      for (final e in list)
        if (e is Map)
          BindingDevice.fromJson(e.cast<String, dynamic>()),
    ];
  }

  @override
  Future<List<BindingDevice>> getOnlineDevices() async {
    final data = await _get('/api/client/devices/online');
    final list = (data['devices'] as List?) ?? [];
    return [
      for (final e in list)
        if (e is Map)
          BindingDevice.fromJson(e.cast<String, dynamic>()),
    ];
  }

  @override
  Future<void> redeemPairingCode(String code) async {
    await _post(
      '/api/client/pairing/redeem',
      body: {'code': code},
      auth: true,
    );
  }

  @override
  Future<Map<String, dynamic>?> turnCredentials() async {
    try {
      return await _get('/api/client/turn-credentials');
    } on Object {
      return null;
    }
  }

  @override
  Future<void> clear() async {
    _accessToken = null;
    await _storage.clear();
  }

  Future<Map<String, dynamic>> _post(
    String path, {
    required Map<String, dynamic> body,
    bool auth = false,
  }) async {
    return _handle(
      await _send(
        () => _dio.post<dynamic>(
          path,
          data: jsonEncode(body),
          options: Options(
            headers: {
              if (auth && _accessToken != null)
                'Authorization': 'Bearer $_accessToken',
            },
          ),
        ),
      ),
    );
  }

  Future<Map<String, dynamic>> _get(String path) async {
    return _handle(
      await _send(
        () => _dio.get<dynamic>(
          path,
          options: Options(
            headers: {
              if (_accessToken != null)
                'Authorization': 'Bearer $_accessToken',
            },
          ),
        ),
      ),
    );
  }

  /// 发送请求，遇 401 时尝试用 refreshToken 续期后重试一次。
  ///
  /// Dio 默认对非 2xx 直接抛出 [DioException]（bad response），不会走到
  /// [_handle]，因此在异常层拦截 401 并触发刷新重试，避免已登录用户因
  /// accessToken 过期而直接失败。
  Future<Response<dynamic>> _send(
    Future<Response<dynamic>> Function() request,
  ) async {
    try {
      return await request();
    } on DioException catch (e) {
      if (e.response?.statusCode == 401 && await hasRefreshToken()) {
        try {
          await refresh();
        } on Object {
          rethrow;
        }
        return await request();
      }
      rethrow;
    }
  }

  Map<String, dynamic> _handle(Response<dynamic> res) {
    final data = res.data;
    final body = (data is Map)
        ? data.cast<String, dynamic>()
        : <String, dynamic>{};
    if (res.statusCode == 401) {
      throw ApiException(body['code'] as String? ?? 'UNAUTHORIZED', 401);
    }
    if (res.statusCode == null || res.statusCode! < 200 || res.statusCode! >= 300) {
      throw ApiException(
        body['error'] as String? ?? 'HTTP ${res.statusCode}',
        res.statusCode ?? -1,
      );
    }
    return body;
  }
}
