import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:math';
import 'package:flutter/services.dart';
import 'package:gbk_codec/gbk_codec.dart';
import 'package:lbjconsole/models/train_record.dart';
import 'package:lbjconsole/services/database_service.dart';

const String _lbjInfoAddr = "1234000";
const String _lbjInfo2Addr = "1234002";
const String _lbjSyncAddr = "1234008";
const int _functionDown = 1;
const int _functionUp = 3;

class _LbJState {
  String train = "<NUL>";
  int direction = -1;
  String speed = "NUL";
  String positionKm = " <NUL>";
  String time = "<NUL>";
  String lbjClass = "NA";
  String loco = "<NUL>";
  String route = "********";

  String posLonDeg = "";
  String posLonMin = "";
  String posLatDeg = "";
  String posLatMin = "";

  String _info2Hex = "";

  void reset() {
    train = "<NUL>";
    direction = -1;
    speed = "NUL";
    positionKm = " <NUL>";
    time = "<NUL>";
    lbjClass = "NA";
    loco = "<NUL>";
    route = "********";
    posLonDeg = "";
    posLonMin = "";
    posLatDeg = "";
    posLatMin = "";
    _info2Hex = "";
  }

  String _recodeBCD(String numericStr) {
    return numericStr
        .replaceAll('.', 'A')
        .replaceAll('U', 'B')
        .replaceAll(' ', 'C')
        .replaceAll('-', 'D')
        .replaceAll(')', 'E')
        .replaceAll('(', 'F');
  }

  int _hexToChar(String hex1, String hex2) {
    final String hex = "$hex1$hex2";
    return int.tryParse(hex, radix: 16) ?? 0;
  }

  String _gbkToUtf8(List<int> gbkBytes) {
    try {
      final validBytes = gbkBytes.where((b) => b != 0).toList();
      print('gbkBytes: ${validBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(',')}');
      
      final result = gbk_bytes.decode(validBytes);
      print('gbk decoded: $result');
      return result;
    } catch (e) {
      print('gbk decode error: $e');
      return "";
    }
  }

  void updateFromRaw(String addr, int func, String numeric) {
    if (func == _functionDown || func == _functionUp) {
      direction = func;
    }
    switch (addr) {
      case _lbjInfoAddr:
        final RegExp infoRegex = RegExp(r'^\s*(\S+)\s+(\S+)\s+(\S+)');
        final match = infoRegex.firstMatch(numeric);
        if (match != null) {
          train = match.group(1) ?? "<NUL>";
          speed = match.group(2) ?? "NUL";

          String pos = match.group(3)?.trim() ?? "";

          if (pos.isEmpty) {
            positionKm = " <NUL>";
          } else if (pos.length > 1) {
            positionKm =
                "${pos.substring(0, pos.length - 1)}.${pos.substring(pos.length - 1)}";
          } else {
            positionKm = "0.$pos";
          }
        }
        break;

      case _lbjInfo2Addr:
        String buffer = numeric;
        if (buffer.length < 50) return;
        _info2Hex = _recodeBCD(buffer);
        print('info2 raw: $buffer');
        print('info2 hex: $_info2Hex');

        if (_info2Hex.length >= 4) {
          try {
            List<int> classBytes = [
              _hexToChar(_info2Hex[0], _info2Hex[1]),
              _hexToChar(_info2Hex[2], _info2Hex[3]),
            ];
            lbjClass = String.fromCharCodes(classBytes
                .where((b) => b > 0x1F && b < 0x7F && b != 0x22 && b != 0x2C));
          } catch (e) {}
        }
        if (buffer.length >= 12) loco = buffer.substring(4, 12);

        List<int> routeBytes = List<int>.filled(17, 0);

        if (_info2Hex.length >= 18) {
          try {
            routeBytes[0] = _hexToChar(_info2Hex[14], _info2Hex[15]);
            routeBytes[1] = _hexToChar(_info2Hex[16], _info2Hex[17]);
          } catch (e) {}
        }

        if (_info2Hex.length >= 22) {
          try {
            routeBytes[2] = _hexToChar(_info2Hex[18], _info2Hex[19]);
            routeBytes[3] = _hexToChar(_info2Hex[20], _info2Hex[21]);
          } catch (e) {}
        }

        if (_info2Hex.length >= 30) {
          try {
            routeBytes[4] = _hexToChar(_info2Hex[22], _info2Hex[23]);
            routeBytes[5] = _hexToChar(_info2Hex[24], _info2Hex[25]);
            routeBytes[6] = _hexToChar(_info2Hex[26], _info2Hex[27]);
            routeBytes[7] = _hexToChar(_info2Hex[28], _info2Hex[29]);
          } catch (e) {}
        }
        route = _gbkToUtf8(routeBytes);

        if (buffer.length >= 39) {
          posLonDeg = buffer.substring(30, 33);
          posLonMin = "${buffer.substring(33, 35)}.${buffer.substring(35, 39)}";
        }
        if (buffer.length >= 47) {
          posLatDeg = buffer.substring(39, 41);
          posLatMin = "${buffer.substring(41, 43)}.${buffer.substring(43, 47)}";
        }
        break;

      case _lbjSyncAddr:
        if (numeric.length >= 5) {
          time = "${numeric.substring(1, 3)}:${numeric.substring(3, 5)}";
        }
        break;
    }
  }

  double _convertMagSqToRssi(double magsqRaw) {
    if (magsqRaw <= 0) return -120.0;
    double rssi = 10 * log(magsqRaw) / log(10);
    return (rssi - 30.0).clamp(-120.0, -20.0);
  }

  Map<String, dynamic> toTrainRecordJson(double magsqRaw) {
    final now = DateTime.now();
    final double finalRssi = _convertMagSqToRssi(magsqRaw);

    String gpsPosition = "";
    if (posLatDeg.isNotEmpty && posLatMin.isNotEmpty) {
      gpsPosition = "$posLatDeg°$posLatMin′";
    }
    if (posLonDeg.isNotEmpty && posLonMin.isNotEmpty) {
      gpsPosition +=
          "${gpsPosition.isEmpty ? "" : " "}$posLonDeg°$posLonMin′";
    }

    String kmPosition = positionKm.replaceAll(' <NUL>', '');

    final jsonData = {
      'uniqueId': '${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}',
      'receivedTimestamp': now.millisecondsSinceEpoch,
      'timestamp': now.millisecondsSinceEpoch,
      'rssi': finalRssi,
      'train': train.replaceAll('<NUL>', ''),
      'loco': loco.replaceAll('<NUL>', ''),
      'speed': speed.replaceAll('NUL', ''),
      'position': kmPosition,
      'positionInfo': gpsPosition,
      'route': route.replaceAll('********', ''),
      'lbjClass': lbjClass.replaceAll('NA', ''),
      'time': time.replaceAll('<NUL>', ''),
      'direction': (direction == 1 || direction == 3) ? direction : 0,
      'locoType': "",
    };
    return jsonData;
  }
}

class RtlTcpService {
  static final RtlTcpService _instance = RtlTcpService._internal();
  factory RtlTcpService() => _instance;
  RtlTcpService._internal();
  static const _methodChannel =
      MethodChannel('org.noxylva.lbjconsole/rtl_tcp_method');
  static const _eventChannel =
      EventChannel('org.noxylva.lbjconsole/rtl_tcp_event');
  final StreamController<String> _statusController =
      StreamController<String>.broadcast();
  final StreamController<TrainRecord> _dataController =
      StreamController<TrainRecord>.broadcast();
  final StreamController<bool> _connectionController =
      StreamController<bool>.broadcast();
  final StreamController<DateTime?> _lastReceivedTimeController =
      StreamController<DateTime?>.broadcast();

  Stream<String> get statusStream => _statusController.stream;
  Stream<TrainRecord> get dataStream => _dataController.stream;
  Stream<bool> get connectionStream => _connectionController.stream;
  Stream<DateTime?> get lastReceivedTimeStream =>
      _lastReceivedTimeController.stream;
  String _deviceStatus = "未连接";
  bool _isConnected = false;
  DateTime? _lastReceivedTime;
  StreamSubscription? _eventChannelSubscription;
  final _LbJState _state = _LbJState();
  Timer? _reconnectTimer;
  String _lastHost = '127.0.0.1';
  String _lastPort = '14423';
  static const Duration _reconnectInterval = Duration(seconds: 2);
  bool _isEnabled = false;

  static const bool _logRaw = true;
  static const bool _logParsed = true;
  String _lastRawMessage = "";

  bool get isConnected => _isConnected;
  String get deviceStatus => _deviceStatus;
  String get currentAddress => '$_lastHost:$_lastPort';
  bool get isEnabled => _isEnabled;

  void _updateConnectionState(bool connected, String status) {
    if (_isConnected == connected && _deviceStatus == status) return;
    final wasConnected = _isConnected;
    _isConnected = connected;
    _deviceStatus = status;
    if (!connected) {
      _lastReceivedTime = null;
      if (wasConnected) _state.reset();
      if (_isEnabled) _startAutoReconnect();
    } else {
      _cancelAutoReconnect();
    }
    _statusController.add(_deviceStatus);
    _connectionController.add(_isConnected);
    _lastReceivedTimeController.add(_lastReceivedTime);
  }

  void _startAutoReconnect() {
    if (!_isEnabled || _reconnectTimer != null) return;
    developer.log('RTL-TCP: 启动自动重连定时器', name: 'RtlTcpService');
    _reconnectTimer = Timer.periodic(_reconnectInterval, (timer) {
      if (_isConnected || !_isEnabled) {
        _cancelAutoReconnect();
        return;
      }
      developer.log('RTL-TCP: 自动重连: 尝试连接...', name: 'RtlTcpService');
      _internalConnect();
    });
  }

  void _cancelAutoReconnect() {
    if (_reconnectTimer != null) {
      developer.log('RTL-TCP: 取消自动重连', name: 'RtlTcpService');
      _reconnectTimer!.cancel();
      _reconnectTimer = null;
    }
  }

  void _listenToEventChannel() {
    if (_eventChannelSubscription != null) {
      _eventChannelSubscription?.cancel();
    }

    _eventChannelSubscription = _eventChannel.receiveBroadcastStream().listen(
      (dynamic event) {
        try {
          final map = event as Map;

          if (map.containsKey('connected')) {
            final connected = map['connected'] as bool? ?? false;
            if (_isConnected != connected) {
              _updateConnectionState(connected, connected ? "已连接" : "已断开");
            }
          }

          if (map.containsKey('address')) {
            final addr = map['address'] as String;

            if (addr != _lbjInfoAddr &&
                addr != _lbjInfo2Addr &&
                addr != _lbjSyncAddr) {
              return;
            }

            final func = int.tryParse(map['func'] as String? ?? '-1') ?? -1;
            final numeric = map['numeric'] as String;
            final magsqRaw = (map['magsqRaw'] as num?)?.toDouble() ?? 0.0;

            final String currentRawMessage = "$addr|$func|$numeric";
            if (currentRawMessage == _lastRawMessage) {
              return;
            }
            _lastRawMessage = currentRawMessage;

            if (_logRaw) {
              developer.log('RTL-TCP-RAW: $currentRawMessage',
                  name: 'RTL-TCP-Data');
            }

            if (!_isConnected) {
              _updateConnectionState(true, "已连接");
            }
            _lastReceivedTime = DateTime.now();
            _lastReceivedTimeController.add(_lastReceivedTime);

            _state.updateFromRaw(addr, func, numeric);

            if (addr == _lbjInfoAddr || addr == _lbjInfo2Addr) {
              final jsonData = _state.toTrainRecordJson(magsqRaw);
              final trainRecord = TrainRecord.fromJson(jsonData);

              if (_logParsed) {
                developer.log('RTL-TCP-PARSED: ${jsonEncode(jsonData)}',
                    name: 'RTL-TCP-Data');
              }

              _dataController.add(trainRecord);
              DatabaseService.instance.insertRecord(trainRecord);
            }
          }
        } catch (e, s) {
          developer.log('RTL-TCP StateMachine Error: $e',
              name: 'RTL-TCP', error: e, stackTrace: s);
          _updateConnectionState(false, "数据解析错误");
        }
      },
      onError: (dynamic error) {
        _updateConnectionState(false, "数据通道错误");
        _eventChannelSubscription?.cancel();
        _eventChannelSubscription = null;
      },
      onDone: () {
        _updateConnectionState(false, "连接已断开");
        _eventChannelSubscription = null;
      },
    );
  }

  Future<void> _internalConnect() async {
    if (_eventChannelSubscription == null) {
      _listenToEventChannel();
    }

    try {
      await _methodChannel
          .invokeMethod('connect', {'host': _lastHost, 'port': _lastPort});
    } on PlatformException catch (e) {
      _updateConnectionState(false, "连接失败: ${e.message}");
    }
  }

  Future<void> connect({String? host, String? port}) async {
    final settings = await DatabaseService.instance.getAllSettings();
    final dbHost = host ?? settings?['rtl_tcp_host'] ?? '127.0.0.1';
    final dbPort = port ?? settings?['rtl_tcp_port'] ?? '14423';
    _isEnabled = true;
    _lastHost = dbHost;
    _lastPort = dbPort;
    if (_isConnected) return;
    _updateConnectionState(false, "正在连接...");
    _internalConnect();
  }

  Future<void> disconnect() async {
    _isEnabled = false;
    _cancelAutoReconnect();
    await _eventChannelSubscription?.cancel();
    _eventChannelSubscription = null;
    try {
      await _methodChannel.invokeMethod('disconnect');
    } catch (e) {
      developer.log('RTL-TCP: 原生断开出错: $e', name: 'RTL-TCP');
    }
    _updateConnectionState(false, "已禁用");
  }

  void dispose() {
    disconnect();
    _statusController.close();
    _dataController.close();
    _connectionController.close();
    _lastReceivedTimeController.close();
  }
}
