// auth_api.dart — 控制面认证 HTTP 客户端
//
// 职责：封装 /v1/auth/* 接口调用。
// 关键约束：所有 HTTP 错误统一包装为 AuthApiException，
// UI 层通过 e.statusCode / e.messageZh 显示错误文案。
import 'dart:convert';

import 'package:http/http.dart' as http;

/// 认证接口调用失败时抛出。
/// [statusCode] < 0 表示网络层错误。
class AuthApiException implements Exception {
  const AuthApiException({
    required this.statusCode,
    required this.messageZh,
    required this.messageEn,
  });

  final int statusCode;
  final String messageZh;
  final String messageEn;

  @override
  String toString() => 'AuthApiException($statusCode): $messageZh';
}

/// 登录 / 注册成功后返回的 JWT 令牌对。
class AuthTokens {
  const AuthTokens({required this.accessToken, required this.refreshToken});

  final String accessToken;
  final String refreshToken;

  factory AuthTokens.fromJson(Map<String, dynamic> m) => AuthTokens(
        accessToken: m['access_token'] as String? ?? '',
        refreshToken: m['refresh_token'] as String? ?? '',
      );
}

/// [AuthApi] 封装 /v1/auth 接口族。
class AuthApi {
  AuthApi({required this.baseUrl, http.Client? client})
      : _client = client ?? http.Client();

  final String baseUrl;
  final http.Client _client;

  // ── 发送验证码：POST /v1/auth/code ─────────────────────────────

  /// 向 [email] 发送 6 位验证码。成功无返回，失败抛 [AuthApiException]。
  Future<void> sendCode(String email) async {
    final resp = await _post('/v1/auth/code', {'email': email});
    if (resp.statusCode != 204 && resp.statusCode != 200) {
      _throwFromResponse(resp);
    }
  }

  // ── 注册：POST /v1/auth/register ───────────────────────────────

  Future<AuthTokens> register({
    required String email,
    required String code,
    required String password,
  }) async {
    final resp = await _post('/v1/auth/register', {
      'email': email,
      'code': code,
      'password': password,
    });
    if (resp.statusCode != 200 && resp.statusCode != 201) {
      _throwFromResponse(resp);
    }
    return AuthTokens.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
  }

  // ── 登录：POST /v1/auth/login ───────────────────────────────────

  Future<AuthTokens> login({
    required String email,
    required String password,
  }) async {
    final resp = await _post('/v1/auth/login', {
      'email': email,
      'password': password,
    });
    if (resp.statusCode != 200) _throwFromResponse(resp);
    return AuthTokens.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
  }

  // ── 刷新 token：POST /v1/auth/refresh ──────────────────────────

  Future<AuthTokens> refresh(String refreshToken) async {
    final resp = await _post('/v1/auth/refresh', {'refresh_token': refreshToken});
    if (resp.statusCode != 200) _throwFromResponse(resp);
    return AuthTokens.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
  }

  // ── 内部 ────────────────────────────────────────────────────────

  Future<http.Response> _post(String path, Map<String, dynamic> body) async {
    final uri = Uri.parse('$baseUrl$path');
    print('>>>AUTHLOG POST $uri (baseUrl=$baseUrl) 发起...');
    final sw = Stopwatch()..start();
    try {
      final resp = await _client
          .post(
            uri,
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode(body),
          )
          .timeout(const Duration(seconds: 15));
      print('>>>AUTHLOG POST $uri -> HTTP ${resp.statusCode} (${sw.elapsedMilliseconds}ms)');
      return resp;
    } on Exception catch (e) {
      print('>>>AUTHLOG POST $uri 失败 (${sw.elapsedMilliseconds}ms): ${e.runtimeType}: $e');
      throw AuthApiException(
        statusCode: -1,
        messageZh: '网络请求失败，请检查连接后重试',
        messageEn: 'Network error: $e',
      );
    }
  }

  void _throwFromResponse(http.Response resp) {
    String messageZh = '操作失败 (HTTP ${resp.statusCode})';
    String messageEn = 'Request failed (HTTP ${resp.statusCode})';
    try {
      final body = jsonDecode(resp.body) as Map<String, dynamic>;
      messageZh = body['message_zh'] as String? ?? messageZh;
      messageEn = body['message_en'] as String? ?? messageEn;
    } catch (_) {}
    throw AuthApiException(
      statusCode: resp.statusCode,
      messageZh: messageZh,
      messageEn: messageEn,
    );
  }

  void dispose() => _client.close();
}
