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

import 'package:laki_icu_app/utils/bluetooth/index.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';

enum LakiMqttConnectionState {
  disconnected,
  connecting,
  connected,
  failed,
}

class LakiMqttConfig {
  const LakiMqttConfig({
    required this.host,
    required this.clientId,
    this.port = 1883,
    this.username,
    this.password,
    this.useSsl = false,
    this.useWebSocket = false,
    this.webSocketProtocols,
    this.keepAliveSeconds = 30,
    this.connectTimeoutMs = 5000,
    this.logging = false,
  });

  factory LakiMqttConfig.tencentTdmq({
    required String deviceSn,
    bool logging = false,
  }) {
    return LakiMqttConfig(
      host: _tencentTdmqHost,
      port: _tencentTdmqPort,
      clientId: '$_tencentTdmqClientIdPrefix$deviceSn',
      username: _tencentTdmqUsername,
      password: _tencentTdmqPassword,
      logging: logging,
    );
  }

  static const String _tencentTdmqHost =
      'mqtt-7jqojva8-sh-public.mqtt.tencenttdmq.com';
  static const int _tencentTdmqPort = 1883;
  static const String _tencentTdmqClientIdPrefix = 'GID_icu@@@';
  static const String _tencentTdmqUsername = 'root';
  static const String _tencentTdmqPassword = 'sk665f2b219cb3700b';

  final String host;
  final int port;
  final String clientId;
  final String? username;
  final String? password;
  final bool useSsl;
  final bool useWebSocket;
  final List<String>? webSocketProtocols;
  final int keepAliveSeconds;
  final int connectTimeoutMs;
  final bool logging;

  bool get isConfigured => host.isNotEmpty && clientId.isNotEmpty;
}

class LakiMqttTopics {
  const LakiMqttTopics._();

  static String icuUp(String deviceSn) => 'icu/$deviceSn/up';

  static String icuTo(String deviceSn) => 'icu/$deviceSn/to';

  static String icuPetBind(String deviceSn) => 'icu/$deviceSn/pet/bind';

  static String cameraTo(String cabinSn) => 'camera/$cabinSn/to';

  static const String clientConnected = r'$events/client_connected';

  static const String clientDisconnected = r'$events/client_disconnected';

  static const String messageDelivered = r'$events/message_delivered';
}

class LakiMqttMessage {
  const LakiMqttMessage({
    required this.topic,
    required this.payload,
    this.json,
  });

  final String topic;
  final String payload;
  final Map<String, dynamic>? json;
}

class LakiMqttClient {
  LakiMqttClient({required LakiMqttConfig config}) : _config = config;

  final LakiMqttConfig _config;
  MqttServerClient? _client;
  StreamSubscription<List<MqttReceivedMessage<MqttMessage>>>? _updatesSub;

  final _stateController =
      StreamController<LakiMqttConnectionState>.broadcast();
  final _messageController = StreamController<LakiMqttMessage>.broadcast();
  final _errorController = StreamController<String>.broadcast();

  Stream<LakiMqttConnectionState> get stateStream => _stateController.stream;

  Stream<LakiMqttMessage> get messageStream => _messageController.stream;

  Stream<String> get errorStream => _errorController.stream;

  bool get isConnected =>
      _client?.connectionStatus?.state == MqttConnectionState.connected;

  Future<void> connect() async {
    if (!_config.isConfigured) {
      throw StateError('MQTT 连接配置未填写，请先配置 host 和 clientId');
    }

    if (isConnected) {
      return;
    }

    _emitState(LakiMqttConnectionState.connecting);

    final client = MqttServerClient(_config.host, _config.clientId);
    client.port = _config.port;
    client.secure = _config.useSsl;
    client.useWebSocket = _config.useWebSocket;
    client.keepAlivePeriod = _config.keepAliveSeconds;
    client.connectTimeoutPeriod = _config.connectTimeoutMs;
    client.onConnected = () => _emitState(LakiMqttConnectionState.connected);
    client.onDisconnected =
        () => _emitState(LakiMqttConnectionState.disconnected);
    client.onSubscribed = (topic) => _emitError('MQTT 已订阅: $topic');
    client.onSubscribeFail = (topic) => _emitError('MQTT 订阅失败: $topic');
    client.pongCallback = () {};

    final webSocketProtocols = _config.webSocketProtocols;
    if (webSocketProtocols != null) {
      client.websocketProtocols = webSocketProtocols;
    }
    client.logging(on: _config.logging);
    client.setProtocolV311();

    client.connectionMessage = MqttConnectMessage()
        .withClientIdentifier(_config.clientId)
        .startClean()
        .withWillQos(MqttQos.atLeastOnce);

    try {
      await client.connect(_config.username, _config.password);
    } on NoConnectionException catch (error) {
      client.disconnect();
      _emitState(LakiMqttConnectionState.failed);
      _emitError('MQTT 连接失败: $error');
      rethrow;
    } on SocketException catch (error) {
      client.disconnect();
      _emitState(LakiMqttConnectionState.failed);
      _emitError('MQTT Socket 连接失败: $error');
      rethrow;
    }

    if (client.connectionStatus?.state != MqttConnectionState.connected) {
      final status = client.connectionStatus;
      client.disconnect();
      _emitState(LakiMqttConnectionState.failed);
      throw StateError('MQTT 连接未成功: $status');
    }

    _client = client;
    _listenUpdates(client);
    _emitState(LakiMqttConnectionState.connected);
  }

  void subscribe(String topic, {MqttQos qos = MqttQos.atLeastOnce}) {
    _requireConnected().subscribe(topic, qos);
  }

  void unsubscribe(String topic) {
    _requireConnected().unsubscribe(topic);
  }

  void subscribeIcuCommands(String deviceSn) {
    subscribe(LakiMqttTopics.icuTo(deviceSn));
  }

  void subscribePetBind(String deviceSn) {
    subscribe(LakiMqttTopics.icuPetBind(deviceSn));
  }

  int publishJson(
    String topic,
    Map<String, dynamic> payload, {
    MqttQos qos = MqttQos.atLeastOnce,
    bool retain = false,
  }) {
    return publishString(
      topic,
      jsonEncode(payload),
      qos: qos,
      retain: retain,
    );
  }

  int publishString(
    String topic,
    String payload, {
    MqttQos qos = MqttQos.atLeastOnce,
    bool retain = false,
  }) {
    final builder = MqttClientPayloadBuilder()..addString(payload);
    return _requireConnected().publishMessage(
      topic,
      qos,
      builder.payload!,
      retain: retain,
    );
  }

  int publishMonitorReport(
    McuReport report, {
    DateTime? date,
    MqttQos qos = MqttQos.atLeastOnce,
  }) {
    if (report.sn.isEmpty) {
      throw ArgumentError.value(report.sn, 'report.sn', '设备 SN 不能为空');
    }

    return publishJson(
      LakiMqttTopics.icuUp(report.sn),
      report.toMonitorPayload(date: date),
      qos: qos,
    );
  }

  int publishCommandResponse({
    required String deviceSn,
    required String eventId,
    required String refEventId,
    required int status,
    required String message,
    MqttQos qos = MqttQos.atLeastOnce,
  }) {
    return publishJson(
      LakiMqttTopics.icuUp(deviceSn),
      {
        'eventId': eventId,
        'refEventId': refEventId,
        'deviceType': 'ICU',
        'timestamp': DateTime.now().millisecondsSinceEpoch,
        'type': 'cmdResponse',
        'data': {
          'status': status,
          'msg': message,
        },
      },
      qos: qos,
    );
  }

  Future<void> disconnect() async {
    await _updatesSub?.cancel();
    _updatesSub = null;
    _client?.disconnect();
    _client = null;
    _emitState(LakiMqttConnectionState.disconnected);
  }

  Future<void> dispose() async {
    await disconnect();
    await _stateController.close();
    await _messageController.close();
    await _errorController.close();
  }

  void _listenUpdates(MqttServerClient client) {
    _updatesSub?.cancel();
    _updatesSub = client.updates?.listen((messages) {
      for (final item in messages) {
        final payload = item.payload;
        if (payload is! MqttPublishMessage) {
          continue;
        }

        final payloadText = MqttPublishPayload.bytesToStringAsString(
          payload.payload.message,
        );
        _messageController.add(
          LakiMqttMessage(
            topic: item.topic,
            payload: payloadText,
            json: _tryDecodeJson(payloadText),
          ),
        );
      }
    });
  }

  MqttServerClient _requireConnected() {
    final client = _client;
    if (client == null || !isConnected) {
      throw StateError('MQTT 未连接');
    }
    return client;
  }

  void _emitState(LakiMqttConnectionState state) {
    if (!_stateController.isClosed) {
      _stateController.add(state);
    }
  }

  void _emitError(String message) {
    if (!_errorController.isClosed) {
      _errorController.add(message);
    }
  }

  Map<String, dynamic>? _tryDecodeJson(String payload) {
    try {
      final decoded = jsonDecode(payload);
      return decoded is Map<String, dynamic> ? decoded : null;
    } catch (_) {
      return null;
    }
  }
}
