import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

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

import '../models/ai_model.dart';
import '../models/api_response.dart';
import '../models/ble_status.dart';
import '../models/character_pack.dart';
import '../models/chat_models.dart';
import '../models/player_status.dart';
import '../models/video_item.dart';
import '../models/wifi_network.dart';
import '../models/wifi_status.dart';

typedef UploadProgressCallback = void Function(double progress);

class HttpApiService {
  HttpApiService({required String baseUrl, http.Client? client})
      : _baseUrl = normalizeBaseUrl(baseUrl),
        _client = client ?? http.Client();

  final http.Client _client;
  String _baseUrl;

  String get baseUrl => _baseUrl;

  set baseUrl(String value) {
    _baseUrl = normalizeBaseUrl(value);
  }

  Uri _uri(String path, [Map<String, String>? queryParameters]) {
    final normalizedPath = path.startsWith('/') ? path : '/$path';
    return Uri.parse('$_baseUrl$normalizedPath').replace(
      queryParameters: queryParameters == null || queryParameters.isEmpty
          ? null
          : queryParameters,
    );
  }

  Future<String> fetchConsolePage() async {
    final response = await _client.get(_uri('/'));
    _ensureSuccess(response);
    return response.body;
  }

  Future<String> fetchConsoleIndexHtml() async {
    final response = await _client.get(_uri('/index.html'));
    _ensureSuccess(response);
    return response.body;
  }

  Future<PlayerStatus> getStatus() => getPlaybackStatus();

  Future<PlayerStatus> getPlaybackStatus() async {
    final response = await _client.get(_uri('/api/status'));
    _ensureSuccess(response);
    return PlayerStatus.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> play() => _postCommand('/api/play');

  Future<ApiResponse> pause() => _postCommand('/api/pause');

  Future<ApiResponse> next() => _postCommand('/api/next');

  Future<ApiResponse> previous() => _postCommand('/api/previous');

  Future<ApiResponse> goto(int index) => _postCommand('/api/goto/$index');

  Future<ApiResponse> gotoIndex(int index) => goto(index);

  Future<List<String>> getPlaylist() async {
    final response = await _client.get(_uri('/api/playlist'));
    _ensureSuccess(response);
    final decoded = _decodeJson(response.body);
    if (decoded is! List) {
      return const <String>[];
    }

    return decoded.map<String>((dynamic item) {
      if (item is String) {
        return item;
      }
      if (item is Map<String, dynamic>) {
        return item['name']?.toString() ?? jsonEncode(item);
      }
      if (item is Map) {
        return item['name']?.toString() ?? jsonEncode(item);
      }
      return item.toString();
    }).toList(growable: false);
  }

  Future<ApiResponse> changeScene(String name) {
    return _postCommand('/api/scene/${Uri.encodeComponent(name)}');
  }

  Future<ApiResponse> switchScene(String name) => changeScene(name);

  Future<ApiResponse> trigger(String name, [String? value]) {
    final suffix = value == null || value.isEmpty
        ? ''
        : '/${Uri.encodeComponent(value)}';
    return _postCommand('/api/trigger/${Uri.encodeComponent(name)}$suffix');
  }

  Future<ApiResponse> triggerEvent(String name, {String? value}) {
    return trigger(name, value);
  }

  Future<WifiStatus> getWifiStatus() async {
    final response = await _client.get(_uri('/api/wifi/status'));
    _ensureSuccess(response);
    return WifiStatus.fromJson(_decodeMap(response.body));
  }

  Future<List<WifiNetwork>> scanWifi() async {
    try {
      final response = await _client.get(_uri('/api/wifi/scan'));
      _ensureSuccess(response);
      return _decodeWifiNetworks(response.body);
    } on ApiException {
      final response = await _client.post(_uri('/api/wifi/scan'));
      _ensureSuccess(response);
      return _decodeWifiNetworks(response.body);
    }
  }

  Future<List<WifiNetwork>> scanWifiViaGet() => scanWifi();

  Future<List<WifiNetwork>> scanWifiViaPost() => scanWifi();

  Future<ApiResponse> connectWifi(String ssid, String password) async {
    final response = await _client.post(
      _uri('/api/wifi/connect'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{
        'ssid': ssid,
        'password': password,
      }),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> startAP([String? ssid, String? password]) async {
    final body = <String, dynamic>{
      if (ssid != null && ssid.isNotEmpty) 'ssid': ssid,
      if (password != null && password.isNotEmpty) 'password': password,
    };

    try {
      final response = await _client.post(
        _uri('/api/wifi/ap/start'),
        headers: _jsonHeaders,
        body: jsonEncode(body),
      );
      _ensureSuccess(response);
      return ApiResponse.fromJson(_decodeMap(response.body));
    } on ApiException {
      final response = await _client.post(
        _uri('/api/wifi/hotspot/start'),
        headers: _jsonHeaders,
        body: jsonEncode(body),
      );
      _ensureSuccess(response);
      return ApiResponse.fromJson(_decodeMap(response.body));
    }
  }

  Future<ApiResponse> startAccessPoint({String? ssid, String? password}) {
    return startAP(ssid, password);
  }

  Future<ApiResponse> startHotspot({String? ssid, String? password}) {
    return startAP(ssid, password);
  }

  Future<ApiResponse> stopAP() async {
    try {
      return await _postCommand('/api/wifi/ap/stop');
    } on ApiException {
      return _postCommand('/api/wifi/hotspot/stop');
    }
  }

  Future<ApiResponse> stopAccessPoint() => stopAP();

  Future<ApiResponse> stopHotspot() => stopAP();

  Future<BleServiceStatus> getBleStatus() async {
    final response = await _client.get(_uri('/api/ble/status'));
    _ensureSuccess(response);
    return BleServiceStatus.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> startBle([String? deviceName]) async {
    final response = await _client.post(
      _uri('/api/ble/start'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{
        if (deviceName != null && deviceName.isNotEmpty)
          'device_name': deviceName,
      }),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> stopBle() => _postCommand('/api/ble/stop');

  Future<List<VideoItem>> getVideos() async {
    final response = await _client.get(_uri('/api/videos'));
    _ensureSuccess(response);
    final decoded = _decodeJson(response.body);
    if (decoded is! List) {
      return const <VideoItem>[];
    }

    return decoded.map<VideoItem>((dynamic item) {
      if (item is Map<String, dynamic>) {
        return VideoItem.fromJson(item);
      }
      if (item is Map) {
        return VideoItem.fromJson(Map<String, dynamic>.from(item));
      }
      return const VideoItem(name: '', size: 0);
    }).where((item) => item.name.isNotEmpty).toList(growable: false);
  }

  Future<ApiResponse> uploadVideo(File file) {
    return _uploadSingleFile('/api/videos/upload', file);
  }

  Future<ApiResponse> uploadVideoWithProgress(
    File file, {
    UploadProgressCallback? onProgress,
  }) {
    return _uploadSingleFile(
      '/api/videos/upload',
      file,
      onProgress: onProgress,
    );
  }

  Future<ApiResponse> uploadVideos(List<String> filePaths) async {
    if (filePaths.isEmpty) {
      throw const ApiException('未选择上传文件');
    }

    final request = http.MultipartRequest('POST', _uri('/api/videos/upload'));
    for (final filePath in filePaths) {
      request.files.add(await http.MultipartFile.fromPath('file', filePath));
    }

    final response = await http.Response.fromStream(await request.send());
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> deleteVideo(String name) {
    return _deleteCommand('/api/videos/${Uri.encodeComponent(name)}');
  }

  Future<Map<String, dynamic>> getConfig() async {
    final response = await _client.get(_uri('/api/config'));
    _ensureSuccess(response);
    return _decodeMap(response.body);
  }

  Future<ApiResponse> updateConfig(Map<String, dynamic> config) async {
    final response = await _client.post(
      _uri('/api/config'),
      headers: _jsonHeaders,
      body: jsonEncode(config),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<Map<String, dynamic>> getDisplayConfig() async {
    final response = await _client.get(_uri('/api/config/display'));
    _ensureSuccess(response);
    return _decodeMap(response.body);
  }

  Future<Map<String, dynamic>> getAvailableConfigs() async {
    final response = await _client.get(_uri('/api/config/available'));
    _ensureSuccess(response);
    return _decodeMap(response.body);
  }

  Future<ApiResponse> switchConfig(String filename) async {
    final response = await _client.post(
      _uri('/api/config/switch'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{'filename': filename}),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<AvailableConfigs> getAvailableCharacterConfigs() async {
    final map = await getAvailableConfigs();
    return AvailableConfigs.fromJson(map);
  }

  // ── M2.1 AI 对话 / 模型管理 ──

  Future<ChatTurnResponse> chatText({
    required String text,
    String? sessionId,
  }) async {
    final response = await _client.post(
      _uri('/api/chat/text'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{
        'text': text,
        if (sessionId != null && sessionId.isNotEmpty) 'session_id': sessionId,
      }),
    );
    // AI 错误也以 JSON ChatResponse 返回（可能是 5xx）
    final map = _decodeMap(response.body);
    final result = ChatTurnResponse.fromJson(map);
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return result;
    }
    if (result.error != null && result.error!.isNotEmpty) {
      return result;
    }
    throw ApiException(
      map['message']?.toString() ?? '对话失败 (${response.statusCode})',
      statusCode: response.statusCode,
    );
  }

  Future<ChatTurnResponse> chatAudio({
    required List<int> bytes,
    String format = 'wav',
    String? sessionId,
  }) async {
    final response = await _client.post(
      _uri('/api/chat/audio'),
      headers: <String, String>{
        'Content-Type': 'application/octet-stream',
        'Accept': 'application/json',
        'X-Audio-Format': format,
        if (sessionId != null && sessionId.isNotEmpty) 'X-Session-Id': sessionId,
      },
      body: bytes,
    );
    final map = _decodeMap(response.body);
    final result = ChatTurnResponse.fromJson(map);
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return result;
    }
    if (result.error != null && result.error!.isNotEmpty) {
      return result;
    }
    throw ApiException(
      map['message']?.toString() ?? '语音对话失败 (${response.statusCode})',
      statusCode: response.statusCode,
    );
  }

  String chatAudioFileUrl(String absolutePath) {
    return _uri(
      '/api/chat/audio/file',
      <String, String>{'path': absolutePath},
    ).toString();
  }

  Future<ModelsSnapshot> getModels() async {
    final response = await _client.get(_uri('/api/models'));
    _ensureSuccess(response);
    return ModelsSnapshot.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> downloadModel(String modelId) async {
    final response = await _client.post(
      _uri('/api/models/download'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{'model_id': modelId}),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> switchModel({
    required String modelId,
    required String kind,
  }) async {
    final response = await _client.post(
      _uri('/api/models/switch'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{
        'model_id': modelId,
        'kind': kind,
      }),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> deleteModel(String modelId) async {
    final response = await _client.post(
      _uri('/api/models/delete'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{'model_id': modelId}),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<List<Map<String, dynamic>>> listFiles(String dirKey, [String? path]) async {
    final response = await _client.get(
      _uri('/api/files/$dirKey', _pathQuery(path)),
    );
    _ensureSuccess(response);
    final decoded = _decodeJson(response.body);
    if (decoded is! List) {
      return const <Map<String, dynamic>>[];
    }

    return decoded
        .whereType<Map>()
        .map((entry) => Map<String, dynamic>.from(entry))
        .toList(growable: false);
  }

  Future<ApiResponse> uploadFile(String dirKey, File file, [String? path]) {
    return _uploadSingleFile(
      '/api/files/$dirKey/upload',
      file,
      directoryPath: path,
    );
  }

  Future<Uint8List> downloadFile(String dirKey, String path) async {
    final response = await _client.get(
      _uri('/api/files/$dirKey/download', <String, String>{'path': path}),
    );
    _ensureSuccess(response, expectJson: false);
    return response.bodyBytes;
  }

  Future<ApiResponse> deleteFile(String dirKey, String path) async {
    final response = await _client.post(
      _uri('/api/files/$dirKey/delete'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{'path': path}),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> mkdir(String dirKey, String path) async {
    final response = await _client.post(
      _uri('/api/files/$dirKey/mkdir'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{'path': path}),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<List<Map<String, dynamic>>> getPlugins() async {
    final response = await _client.get(_uri('/api/plugins'));
    _ensureSuccess(response);
    final decoded = _decodeJson(response.body);
    if (decoded is! List) {
      return const <Map<String, dynamic>>[];
    }

    return decoded
        .whereType<Map>()
        .map((plugin) => Map<String, dynamic>.from(plugin))
        .toList(growable: false);
  }

  Future<ApiResponse> enablePlugin(String id) {
    return _postCommand('/api/plugins/${Uri.encodeComponent(id)}/enable');
  }

  Future<ApiResponse> disablePlugin(String id) {
    return _postCommand('/api/plugins/${Uri.encodeComponent(id)}/disable');
  }

  Future<ApiResponse> installPlugin(String id, [String? version]) async {
    final response = await _client.post(
      _uri('/api/plugins/install'),
      headers: _jsonHeaders,
      body: jsonEncode(<String, dynamic>{
        'id': id,
        if (version != null && version.isNotEmpty) 'version': version,
      }),
    );
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> _uploadSingleFile(
    String endpoint,
    File file, {
    String? directoryPath,
    UploadProgressCallback? onProgress,
  }) async {
    final request = http.MultipartRequest(
      'POST',
      _uri(endpoint, _pathQuery(directoryPath)),
    );
    request.files.add(
      await _createMultipartFile(
        file,
        fieldName: 'file',
        onProgress: onProgress,
      ),
    );
    final response = await http.Response.fromStream(await request.send());
    onProgress?.call(1);
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<http.MultipartFile> _createMultipartFile(
    File file, {
    required String fieldName,
    UploadProgressCallback? onProgress,
  }) async {
    final length = await file.length();
    final filename = file.uri.pathSegments.isNotEmpty
        ? file.uri.pathSegments.last
        : file.path;

    if (onProgress == null) {
      return http.MultipartFile.fromPath(fieldName, file.path, filename: filename);
    }

    var uploaded = 0;
    final stream = http.ByteStream(
      file.openRead().transform(
        StreamTransformer<List<int>, List<int>>.fromHandlers(
          handleData: (chunk, sink) {
            uploaded += chunk.length;
            if (length > 0) {
              final progress = (uploaded / length).clamp(0.0, 1.0);
              onProgress(progress);
            }
            sink.add(chunk);
          },
        ),
      ),
    );

    return http.MultipartFile(
      fieldName,
      stream,
      length,
      filename: filename,
    );
  }

  Future<ApiResponse> _postCommand(String path) async {
    final response = await _client.post(_uri(path));
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  Future<ApiResponse> _deleteCommand(String path) async {
    final response = await _client.delete(_uri(path));
    _ensureSuccess(response);
    return ApiResponse.fromJson(_decodeMap(response.body));
  }

  List<WifiNetwork> _decodeWifiNetworks(String body) {
    final decoded = _decodeJson(body);
    if (decoded is! List) {
      return const <WifiNetwork>[];
    }

    return decoded.map<WifiNetwork>((dynamic item) {
      if (item is Map<String, dynamic>) {
        return WifiNetwork.fromJson(item);
      }
      if (item is Map) {
        return WifiNetwork.fromJson(Map<String, dynamic>.from(item));
      }
      return const WifiNetwork(ssid: '', signal: 0, security: 'Unknown');
    }).where((network) => network.ssid.isNotEmpty).toList(growable: false);
  }

  dynamic _decodeJson(String body) {
    try {
      return jsonDecode(body);
    } on FormatException catch (error) {
      throw ApiException('JSON 解析失败: ${error.message}');
    }
  }

  Map<String, dynamic> _decodeMap(String body) {
    final decoded = _decodeJson(body);
    if (decoded is Map<String, dynamic>) {
      return decoded;
    }
    if (decoded is Map) {
      return Map<String, dynamic>.from(decoded);
    }
    throw const ApiException('期望返回 JSON 对象');
  }

  Map<String, String>? _pathQuery(String? path) {
    if (path == null || path.isEmpty) {
      return null;
    }
    return <String, String>{'path': path};
  }

  void _ensureSuccess(http.Response response, {bool expectJson = true}) {
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return;
    }

    if (!expectJson) {
      throw ApiException(
        '请求失败 (${response.statusCode}): ${response.body}',
        statusCode: response.statusCode,
      );
    }

    String message = response.body;
    try {
      final decoded = _decodeJson(response.body);
      if (decoded is Map) {
        message = decoded['message']?.toString() ?? response.body;
      }
    } on ApiException {
      message = response.body;
    }

    throw ApiException(message, statusCode: response.statusCode);
  }

  Map<String, String> get _jsonHeaders => const <String, String>{
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      };

  void dispose() {
    _client.close();
  }

  static String normalizeBaseUrl(String raw) {
    final trimmed = raw.trim();
    if (trimmed.isEmpty) {
      throw const ApiException('baseUrl 不能为空');
    }

    final withScheme = trimmed.startsWith('http://') || trimmed.startsWith('https://')
        ? trimmed
        : 'http://$trimmed';
    return withScheme.endsWith('/')
        ? withScheme.substring(0, withScheme.length - 1)
        : withScheme;
  }
}

class ApiException implements Exception {
  const ApiException(this.message, {this.statusCode});

  final String message;
  final int? statusCode;

  @override
  String toString() => message;
}
