import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
import '../theme/mesh_theme.dart';
import '../utils/app_logger.dart';
import '../utils/platform_info.dart';
import '../utils/usb_port_labels.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/empty_state.dart';
import '../widgets/mesh_ui.dart';
import '../helpers/snack_bar_builder.dart';
import 'channels_screen.dart';
import 'tcp_screen.dart';

class UsbScreen extends StatefulWidget {
  const UsbScreen({super.key});

  @override
  State<UsbScreen> createState() => _UsbScreenState();
}

class _UsbScreenState extends State<UsbScreen> {
  final List<String> _ports = <String>[];
  bool _isLoadingPorts = true;
  bool _navigatedToChannels = false;
  bool _didScheduleInitialLoad = false;
  Timer? _hotPlugTimer;
  late final MeshCoreConnector _connector;
  late final VoidCallback _connectionListener;

  bool get _supportsHotPlug =>
      PlatformInfo.isWindows || PlatformInfo.isLinux || PlatformInfo.isMacOS;

  @override
  void initState() {
    super.initState();
    _connector = context.read<MeshCoreConnector>();
    _connectionListener = () {
      if (!mounted) return;
      if (_connector.state == MeshCoreConnectionState.disconnected) {
        _navigatedToChannels = false;
      }
      if (_connector.state == MeshCoreConnectionState.connected &&
          _connector.isUsbTransportConnected &&
          !_navigatedToChannels) {
        _navigatedToChannels = true;
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const ChannelsScreen()),
        );
      }
    };
    _connector.addListener(_connectionListener);
    _startHotPlugTimer();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _connector.setUsbRequestPortLabel(context.l10n.usbScreenStatus);
    _connector.setUsbFallbackDeviceName(context.l10n.usbFallbackDeviceName);
    if (!_didScheduleInitialLoad) {
      _didScheduleInitialLoad = true;
      unawaited(_loadPorts());
    }
  }

  @override
  void dispose() {
    _hotPlugTimer?.cancel();
    _hotPlugTimer = null;
    _connector.removeListener(_connectionListener);
    if (!_navigatedToChannels &&
        _connector.activeTransport == MeshCoreTransportType.usb &&
        _connector.state != MeshCoreConnectionState.disconnected) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        unawaited(_connector.disconnect(manual: true));
      });
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () => Navigator.of(context).maybePop(),
        ),
        title: AdaptiveAppBarTitle(context.l10n.usbScreenTitle),
        centerTitle: true,
      ),
      body: SafeArea(
        top: false,
        child: Consumer<MeshCoreConnector>(
          builder: (context, connector, child) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                // Status header
                Padding(
                  padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
                  child: AnimatedSwitcher(
                    duration: const Duration(milliseconds: 300),
                    child: Align(
                      key: ValueKey('${connector.state}_$_isLoadingPorts'),
                      alignment: Alignment.centerLeft,
                      child: _buildStatusChip(context, connector),
                    ),
                  ),
                ),

                // Transport switcher
                _buildTransportLinks(context),

                // Port list
                Expanded(child: _buildPortList(context, connector)),
              ],
            );
          },
        ),
      ),
      bottomNavigationBar: _supportsHotPlug
          ? null
          : SafeArea(
              top: false,
              minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  FloatingActionButton.extended(
                    onPressed: _isLoadingPorts ? null : _loadPorts,
                    heroTag: 'usb_refresh_action',
                    icon: _isLoadingPorts
                        ? const SizedBox(
                            width: 20,
                            height: 20,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          )
                        : const Icon(Icons.usb),
                    label: Text(context.l10n.scanner_scan),
                  ),
                ],
              ),
            ),
    );
  }

  Widget _buildStatusChip(BuildContext context, MeshCoreConnector connector) {
    final l10n = context.l10n;
    final scheme = Theme.of(context).colorScheme;

    if (_isLoadingPorts) {
      return StatusChip(
        label: l10n.usbStatus_searching,
        color: scheme.primary,
        pulse: true,
      );
    } else if (connector.isUsbTransportConnected) {
      switch (connector.state) {
        case MeshCoreConnectionState.connected:
          return StatusChip(
            label: l10n.scanner_connectedTo(
              connector.activeUsbPortDisplayLabel ?? 'USB',
            ),
            color: MeshPalette.signal,
          );
        case MeshCoreConnectionState.disconnecting:
          return StatusChip(
            label: l10n.scanner_disconnecting,
            color: MeshPalette.warn,
            pulse: true,
          );
        default:
          return StatusChip(
            label: l10n.usbStatus_notConnected,
            color: scheme.onSurfaceVariant,
          );
      }
    } else if (connector.state == MeshCoreConnectionState.connecting &&
        connector.activeTransport == MeshCoreTransportType.usb) {
      return StatusChip(
        label: l10n.usbStatus_connecting,
        color: MeshPalette.warn,
        pulse: true,
      );
    } else {
      return StatusChip(
        label: l10n.usbStatus_notConnected,
        color: scheme.onSurfaceVariant,
      );
    }
  }

  Widget _buildTransportLinks(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Wrap(
        spacing: 12,
        runSpacing: 8,
        children: [
          if (!PlatformInfo.isWeb)
            OutlinedButton.icon(
              onPressed: () {
                Navigator.of(context).pushReplacement(
                  MaterialPageRoute(builder: (_) => const TcpScreen()),
                );
              },
              icon: const Icon(Icons.lan),
              label: Text(context.l10n.connectionChoiceTcpLabel),
            ),
          OutlinedButton.icon(
            onPressed: () => Navigator.of(context).maybePop(),
            icon: const Icon(Icons.bluetooth),
            label: Text(context.l10n.connectionChoiceBluetoothLabel),
          ),
        ],
      ),
    );
  }

  Widget _buildPortList(BuildContext context, MeshCoreConnector connector) {
    final l10n = context.l10n;

    if (_isLoadingPorts) {
      return EmptyState(icon: Icons.usb, title: l10n.usbStatus_searching);
    }

    if (_ports.isEmpty) {
      return EmptyState(icon: Icons.usb, title: l10n.usbScreenEmptyState);
    }

    final isConnecting =
        connector.state == MeshCoreConnectionState.connecting &&
        connector.activeTransport == MeshCoreTransportType.usb;

    return ListView.builder(
      padding: const EdgeInsets.only(bottom: 32),
      itemCount: _ports.length,
      itemBuilder: (context, index) {
        final port = _ports[index];
        final displayName = friendlyUsbPortName(port);
        final rawName = normalizeUsbPortName(port);
        final showRawName =
            rawName != displayName && !rawName.startsWith('web:');

        return ListEntrance(
          index: index,
          child: MeshCard(
            padding: EdgeInsets.zero,
            child: ListTile(
              onTap: isConnecting
                  ? null
                  : () {
                      HapticFeedback.selectionClick();
                      _connectPort(port);
                    },
              leading: AvatarCircle(
                name: displayName,
                size: 40,
                icon: Icons.usb,
                color: Theme.of(context).colorScheme.primary,
              ),
              title: Text(
                displayName,
                style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                  fontWeight: FontWeight.w600,
                  color: Theme.of(context).colorScheme.onSurface,
                ),
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
              subtitle: showRawName
                  ? Text(
                      rawName,
                      style: MeshTheme.mono(
                        fontSize: 11,
                        color: Theme.of(context).colorScheme.onSurfaceVariant,
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    )
                  : null,
              trailing: Icon(
                Icons.chevron_right,
                size: 18,
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
          ),
        );
      },
    );
  }

  void _startHotPlugTimer() {
    if (!_supportsHotPlug) return;
    _hotPlugTimer?.cancel();
    _hotPlugTimer = Timer.periodic(const Duration(seconds: 2), (_) {
      _pollHotPlug();
    });
  }

  Future<void> _pollHotPlug() async {
    if (_isLoadingPorts) return;
    if (!mounted) return;
    // Don't poll while connecting or connected.
    if (_connector.state != MeshCoreConnectionState.disconnected) return;
    try {
      final ports = await _connector.listUsbPorts();
      if (!mounted) return;
      final added = ports.where((p) => !_ports.contains(p)).toList();
      final removed = _ports.where((p) => !ports.contains(p)).toList();
      if (added.isEmpty && removed.isEmpty) return;
      setState(() {
        _ports
          ..clear()
          ..addAll(ports);
      });
    } catch (_) {
      // Silent — hot-plug failures are non-critical.
    }
  }

  Future<void> _loadPorts() async {
    if (!mounted) return;
    _connector.setUsbRequestPortLabel(context.l10n.usbScreenStatus);

    setState(() {
      _isLoadingPorts = true;
    });

    try {
      final ports = await _connector.listUsbPorts();
      if (!mounted) return;
      setState(() {
        _ports
          ..clear()
          ..addAll(ports);
        _isLoadingPorts = false;
      });
    } catch (error) {
      if (!mounted) return;
      setState(() {
        _ports.clear();
        _isLoadingPorts = false;
      });
      _showError(error);
    }
  }

  Future<void> _connectPort(String port) async {
    if (_connector.state != MeshCoreConnectionState.disconnected) return;

    final rawPortName = normalizeUsbPortName(port);
    appLogger.info(
      'Connect tapped for $port (raw: $rawPortName)',
      tag: 'UsbScreen',
    );

    try {
      await _connector.connectUsb(portName: rawPortName);
    } catch (error, stackTrace) {
      appLogger.error(
        'Connect failed for $rawPortName: $error\n$stackTrace',
        tag: 'UsbScreen',
      );
      if (!mounted) return;
      _showError(error);
      unawaited(_loadPorts());
    }
  }

  void _showError(Object error) {
    if (!mounted) return;
    // Cancelling the browser's serial port picker is a normal user action, not
    // an error — don't show a scary red toast (and never leak the raw
    // DOMException text).
    if (_isUserCancelledPortPicker(error)) return;
    showDismissibleSnackBar(
      context,
      content: Text(_friendlyErrorMessage(error)),
      backgroundColor: Theme.of(context).colorScheme.error,
    );
  }

  bool _isUserCancelledPortPicker(Object error) {
    if (error is StateError &&
        error.message.contains('No USB serial device selected')) {
      return true;
    }
    final text = error.toString();
    return text.contains('No port selected by the user') ||
        text.contains("Failed to execute 'requestPort'");
  }

  String _friendlyErrorMessage(Object error) {
    final l10n = context.l10n;

    if (error is PlatformException) {
      switch (error.code) {
        case 'usb_permission_denied':
          return l10n.usbErrorPermissionDenied;
        case 'usb_device_missing':
        case 'usb_device_detached':
          return l10n.usbErrorDeviceMissing;
        case 'usb_invalid_port':
          return l10n.usbErrorInvalidPort;
        case 'usb_busy':
          return l10n.usbErrorBusy;
        case 'usb_not_connected':
          return l10n.usbErrorNotConnected;
        case 'usb_open_failed':
        case 'usb_driver_missing':
          return l10n.usbErrorOpenFailed;
        case 'usb_connect_failed':
          return l10n.usbErrorConnectFailed;
      }
    }

    if (error is UnsupportedError) {
      return l10n.usbErrorUnsupported;
    }

    if (error is StateError) {
      final msg = error.message;
      if (msg.contains('already active')) return l10n.usbErrorAlreadyActive;
      if (msg.contains('No USB serial device selected')) {
        return l10n.usbErrorNoDeviceSelected;
      }
      if (msg.contains('not open') || msg.contains('closed')) {
        return l10n.usbErrorPortClosed;
      }
      if (msg.contains('Timed out')) return l10n.usbErrorConnectTimedOut;
      if (msg.contains('Failed to open')) return l10n.usbErrorOpenFailed;
    }

    if (error is TimeoutException) {
      return l10n.usbErrorConnectTimedOut;
    }

    return error.toString();
  }
}
