import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:livekit_client/livekit_client.dart';
import 'package:matrix/matrix.dart' hide Room;
import 'package:pyramid/core/ice_servers.dart';
import 'package:pyramid/core/livekit_token.dart';
import 'package:shared_preferences/shared_preferences.dart';

const _kVoicePresenceType = 'io.pyramid.voice.presence';

class LiveKitCallManager with ChangeNotifier {
  static final LiveKitCallManager instance = LiveKitCallManager._();
  LiveKitCallManager._();

  // ── Publish quality presets (Streamer → Server) ──────────────────────────
  static final Map<String, VideoParameters> qualityPresets = {
    'sd': VideoParametersPresets.h360_169,
    'hd': VideoParametersPresets.h720_169,
    'fhd': VideoParametersPresets.h1080_169,
    '4k': VideoParametersPresets.h2160_169,
  };

  static const Map<String, VideoEncoding> _screenShareEncodings = {
    'sd':  VideoEncoding(maxBitrate: 500_000,   maxFramerate: 10),
    'hd':  VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15),
    'fhd': VideoEncoding(maxBitrate: 2_500_000, maxFramerate: 20),
    '4k':  VideoEncoding(maxBitrate: 4_000_000, maxFramerate: 30),
  };

  Room? _room;
  EventsListener<RoomEvent>? _roomEventListener;
  String _displayName = '';
  bool isConnecting = false;
  String? error;
  bool isMuted = false;
  bool isCameraOff = false;
  bool isScreenSharing = false;
  String _localQualityKey = 'hd';
  String _subscribeQualityKey = 'auto'; // 'auto' | 'low' | 'medium' | 'high'
  String? _currentScreenShareSourceId;
  LocalVideoTrack? _screenShareTrack;
  bool isDeafened = false;
  bool _mutedBeforeDeafen = false;
  bool isVoiceChannel = false;
  String? _matrixRoomId;
  Client? _matrixClient;
  Timer? _presenceHeartbeat;

  Timer? _statsTimer;

  bool get isActive => _room != null || isConnecting;
  Room? get room => _room;
  String? get currentRoomId => _matrixRoomId;
  String get displayName => _displayName;
  bool get noiseSuppression => false;
  String get currentQualityKey => _localQualityKey;
  String get subscribeQualityKey => _subscribeQualityKey;

  VideoParameters get _currentQuality =>
      qualityPresets[_localQualityKey] ?? VideoParametersPresets.h720_169;

  void _onRoomChanged() => notifyListeners();

  void _cleanUpAfterDisconnect() {
    _stopStatsLogging();
    _presenceHeartbeat?.cancel();
    _presenceHeartbeat = null;
    if (_matrixClient != null && _matrixRoomId != null) {
      _writePresence(false).catchError((_) {});
    }
    _roomEventListener?.dispose();
    _roomEventListener = null;
    _room?.removeListener(_onRoomChanged);
    _room = null;
    _displayName = '';
    isConnecting = false;
    error = null;
    isMuted = false;
    isCameraOff = false;
    isScreenSharing = false;
    isDeafened = false;
    isVoiceChannel = false;
    _mutedBeforeDeafen = false;
    _screenShareTrack = null;
    _currentScreenShareSourceId = null;
    _matrixRoomId = null;
    _matrixClient = null;
    notifyListeners();
  }

  Future<void> _writePresence(bool active) async {
    final client = _matrixClient;
    final roomId = _matrixRoomId;
    final userId = client?.userID;
    if (client == null || roomId == null || userId == null) return;
    try {
      Map<String, dynamic> content;
      if (active) {
        // Resolve display name + avatar from any joined room's member state
        User? member;
        for (final r in client.rooms) {
          final participants = r.getParticipants();
          try {
            member = participants.firstWhere((u) => u.id == userId);
            if (member.displayName != null || member.avatarUrl != null) break;
          } catch (_) {}
        }
        content = {
          'active': true,
          'joined_at': DateTime.now().millisecondsSinceEpoch,
          'display_name': member?.displayName ?? userId,
          if (member?.avatarUrl != null) 'avatar_url': member!.avatarUrl.toString(),
        };
      } else {
        content = {'active': false};
      }
      await client.setRoomStateWithKey(roomId, _kVoicePresenceType, userId, content);
    } catch (e) {
      debugPrint('[VoicePresence] $e');
    }
  }

  Future<void> startCall({
    required String roomName,
    required String roomDisplayName,
    required String identity,
    bool audioOnly = false,
    bool voiceChannel = false,
    Client? matrixClient,
    String? matrixRoomId,
  }) async {
    if (isActive) return;

    final url = 'wss://livekit.steggi-matrix.work';

    isConnecting = true;
    isVoiceChannel = voiceChannel;
    error = null;
    _displayName = roomDisplayName;
    isCameraOff = audioOnly;
    isMuted = false;
    isScreenSharing = false;
    isDeafened = false;
    _mutedBeforeDeafen = false;
    _localQualityKey = 'hd';
    _subscribeQualityKey = 'auto';
    _matrixClient = matrixClient;
    _matrixRoomId = matrixRoomId;
    notifyListeners();

    try {
      final token = LiveKitTokenGenerator.generate(
        roomName: roomName,
        identity: identity,
        displayName: identity,
        ttlSeconds: 21600,
      );

      final room = Room(
        roomOptions: const RoomOptions(
          adaptiveStream: false, // Matches FluffyChat fix
          dynacast: true,
        ),
      );
      room.addListener(_onRoomChanged);

      // Eigene ICE-Server (STUN/TURN aus ice.json) mitgeben. Client-seitige
      // iceServers ERSETZEN die vom LiveKit-Server gelieferten — die zeigten
      // bisher auf einen hinter CGNAT unerreichbaren coturn.
      RTCConfiguration rtcConfig = const RTCConfiguration();
      try {
        final ice = await IceServers.get();
        rtcConfig = RTCConfiguration(
          iceServers: ice
              .map((m) => RTCIceServer(
                    urls: m['urls'] is List
                        ? List<String>.from(m['urls'] as List)
                        : [m['urls'] as String],
                    username: m['username'] as String?,
                    credential: m['credential'] as String?,
                  ))
              .toList(),
        );
      } catch (e) {
        debugPrint('[LiveKit] ICE server fetch error: $e');
      }

      await room.connect(
        url,
        token,
        connectOptions: ConnectOptions(rtcConfiguration: rtcConfig),
      );

      _room = room;
      isConnecting = false;
      notifyListeners();

      if (voiceChannel && matrixClient != null && matrixRoomId != null) {
        await _writePresence(true);
        _presenceHeartbeat = Timer.periodic(const Duration(seconds: 30), (_) {
          _writePresence(true).catchError((_) {});
        });
      }

      _roomEventListener = room.createListener()
        ..on<LocalTrackPublishedEvent>((_) => notifyListeners())
        ..on<LocalTrackUnpublishedEvent>((event) {
          if (event.publication.source == TrackSource.screenShareVideo) {
            isScreenSharing = false;
            _currentScreenShareSourceId = null;
            _screenShareTrack = null;
          }
          notifyListeners();
        })
        ..on<TrackSubscribedEvent>((event) {
          final pub = event.publication;
          if (_subscribeQualityKey != 'auto' && pub.kind == TrackType.VIDEO) {
            pub.setVideoQuality(_videoQualityFromKey(_subscribeQualityKey));
          }
          // Ausgabelautstärke aus den Einstellungen auf neue Audio-Tracks.
          if (pub.kind == TrackType.AUDIO) {
            try {
              rtc.Helper.setVolume(
                  _outputVolume.clamp(0.0, 2.0), event.track.mediaStreamTrack);
            } catch (_) {}
          }
          notifyListeners();
        })
        ..on<TrackUnsubscribedEvent>((_) => notifyListeners())
        ..on<TrackPublishedEvent>((_) => notifyListeners())
        ..on<TrackUnpublishedEvent>((_) => notifyListeners())
        ..on<ParticipantConnectedEvent>((_) => notifyListeners())
        ..on<ParticipantDisconnectedEvent>((_) => notifyListeners())
        ..on<RoomDisconnectedEvent>((_) {
          _cleanUpAfterDisconnect();
        });

      // In den Einstellungen gewählte Geräte anwenden (voice_*_device Prefs).
      // Stale-IDs (abgestecktes Headset etc.) werden gegen die aktuell
      // vorhandenen Geräte validiert und sonst ignoriert.
      String? prefMic, prefSpeaker, prefCam;
      var deviceIds = <String>{};
      try {
        final prefs = await SharedPreferences.getInstance();
        _outputVolume = prefs.getDouble('voice_output_volume') ?? 1.0;
        prefMic = prefs.getString('voice_mic_device');
        prefSpeaker = prefs.getString('voice_speaker_device');
        prefCam = prefs.getString('voice_camera_device');
        final devices = await rtc.navigator.mediaDevices.enumerateDevices();
        deviceIds = devices.map((d) => d.deviceId).toSet();
      } catch (e) {
        debugPrint('[LiveKit] Device prefs/enumerate error: $e');
      }
      if (prefMic != null && !deviceIds.contains(prefMic)) prefMic = null;
      if (prefSpeaker != null && !deviceIds.contains(prefSpeaker)) prefSpeaker = null;
      if (prefCam != null && !deviceIds.contains(prefCam)) prefCam = null;

      await room.localParticipant?.setMicrophoneEnabled(
        true,
        audioCaptureOptions:
            prefMic != null ? AudioCaptureOptions(deviceId: prefMic) : null,
      );

      if (prefSpeaker != null) {
        try {
          await rtc.Helper.selectAudioOutput(prefSpeaker);
        } catch (e) {
          debugPrint('[LiveKit] selectAudioOutput error: $e');
        }
      }

      if (!audioOnly) {
        try {
          await room.localParticipant?.setCameraEnabled(
            true,
            cameraCaptureOptions: CameraCaptureOptions(
              deviceId: prefCam,
              params: _currentQuality,
            ),
          );
        } catch (e) {
          debugPrint('[LiveKit] Failed to enable camera: $e');
        }
      }

      _startStatsLogging();
    } catch (e) {
      debugPrint('[VOIP] Start call error: $e');
      isConnecting = false;
      error = e.toString();
      _cleanUpAfterDisconnect();
    }
  }

  VideoQuality _videoQualityFromKey(String key) {
    switch (key) {
      case 'low':    return VideoQuality.LOW;
      case 'medium': return VideoQuality.MEDIUM;
      case 'high':   return VideoQuality.HIGH;
      default:       return VideoQuality.HIGH; // auto defaults to high
    }
  }

  // ── Ausgabelautstärke (Settings-Slider "voice_output_volume") ─────────────
  double _outputVolume = 1.0;

  /// Live-Anwendung des Lautstärke-Sliders auf alle Remote-Audio-Tracks.
  Future<void> setOutputVolume(double v) async {
    _outputVolume = v;
    final room = _room;
    if (room == null) return;
    for (final p in room.remoteParticipants.values) {
      for (final pub in p.audioTrackPublications) {
        final t = pub.track;
        if (t != null) {
          try {
            await rtc.Helper.setVolume(v.clamp(0.0, 2.0), t.mediaStreamTrack);
          } catch (_) {}
        }
      }
    }
  }

  Future<void> setSubscribeQuality(String key) async {
    debugPrint('[LiveKit] Viewer changing subscribe quality to: $key');
    _subscribeQualityKey = key;
    if (_room == null) { notifyListeners(); return; }

    final quality = _videoQualityFromKey(key);
    for (final p in _room!.remoteParticipants.values) {
      for (final pub in p.videoTrackPublications) {
        if (pub.kind == TrackType.VIDEO) {
          debugPrint('[LiveKit] Setting subscription quality for ${p.identity} to $quality');
          pub.setVideoQuality(quality);
        }
      }
    }
    notifyListeners();
  }

  Future<void> toggleMute() async {
    isMuted = !isMuted;
    notifyListeners();
    await _room?.localParticipant?.setMicrophoneEnabled(!isMuted);
  }

  Future<void> toggleDeafen() async {
    if (!isDeafened) {
      _mutedBeforeDeafen = isMuted;
      isDeafened = true;
      if (!isMuted) {
        await _room?.localParticipant?.setMicrophoneEnabled(false);
        isMuted = true;
      }
    } else {
      isDeafened = false;
      if (!_mutedBeforeDeafen) {
        await _room?.localParticipant?.setMicrophoneEnabled(true);
        isMuted = false;
      }
    }
    _applyDeafen();
    notifyListeners();
  }

  void _applyDeafen() {
    if (_room == null) return;
    for (final p in _room!.remoteParticipants.values) {
      for (final trackPub in p.audioTrackPublications) {
        final track = trackPub.track;
        if (track != null) {
          track.mediaStreamTrack.enabled = !isDeafened;
        }
      }
    }
  }

  Future<void> toggleCamera() async {
    if (isCameraOff) {
      try {
        await _room?.localParticipant?.setCameraEnabled(
          true,
          cameraCaptureOptions: CameraCaptureOptions(params: _currentQuality),
        );
        isCameraOff = false;
      } catch (e) {
        debugPrint('[LiveKit] toggleCamera ON error: $e');
      }
    } else {
      try {
        await _room?.localParticipant?.setCameraEnabled(false);
      } catch (e) {
        debugPrint('[LiveKit] toggleCamera OFF error: $e');
      }
      isCameraOff = true;
    }
    notifyListeners();
  }

  Future<void> toggleScreenShare() async {
    if (isScreenSharing) {
      await stopScreenShare();
    } else {
      try {
        if (!kIsWeb && defaultTargetPlatform == TargetPlatform.windows) {
          final sources = await rtc.desktopCapturer.getSources(types: [rtc.SourceType.Screen, rtc.SourceType.Window]);
          if (sources.isNotEmpty) {
            await startScreenShare(sources.first.id);
            return;
          }
        }
        
        await _room?.localParticipant?.setScreenShareEnabled(true);
        isScreenSharing = true;
        notifyListeners();
      } catch (e) {
        debugPrint('[ScreenShare] Toggle error: $e');
      }
    }
  }

  VideoEncoding _screenShareEncoding() {
    return _screenShareEncodings[_localQualityKey] ??
        const VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15);
  }

  Future<void> startScreenShare(String sourceId) async {
    if (isScreenSharing) return;
    try {
      final encoding = _screenShareEncoding();
      final track = await LocalVideoTrack.createScreenShareTrack(
        ScreenShareCaptureOptions(
          sourceId: sourceId,
          maxFrameRate: encoding.maxFramerate.toDouble(),
        ),
      );

      if (_room == null || _room!.localParticipant == null) return;

      await _room!.localParticipant!.publishVideoTrack(
        track,
        publishOptions: VideoPublishOptions(
          simulcast: true,
          degradationPreference: DegradationPreference.balanced,
          screenShareEncoding: encoding,
        ),
      );

      isScreenSharing = true;
      _currentScreenShareSourceId = sourceId;
      _screenShareTrack = track;
      notifyListeners();
      unawaited(_applyFpsToAllSimulcastLayers(track));
    } catch (e) {
      debugPrint('[ScreenShare] ERROR: $e');
    }
  }

  Future<void> _applyFpsToAllSimulcastLayers(LocalVideoTrack track) async {
    final targetFps = _screenShareEncoding().maxFramerate;
    for (var attempt = 1; attempt <= 5; attempt++) {
      await Future.delayed(const Duration(milliseconds: 600));
      if (!isScreenSharing || _screenShareTrack != track) return;
      final sender = track.transceiver?.sender;
      if (sender == null) continue;
      final params = sender.parameters;
      final encodings = params.encodings;
      if (encodings == null || encodings.isEmpty) continue;
      var anyUpdated = false;
      for (final enc in encodings) {
        if ((enc.maxFramerate ?? 0) < targetFps) {
          enc.maxFramerate = targetFps;
          anyUpdated = true;
        }
      }
      if (anyUpdated) await sender.setParameters(params);
      return;
    }
  }

  Future<void> stopScreenShare() async {
    final local = _room?.localParticipant;
    if (local != null) {
      final toStop = <String>[];
      for (final pub in local.videoTrackPublications) {
        if (pub.source == TrackSource.screenShareVideo) {
          toStop.add(pub.sid);
          await pub.track?.stop();
        }
      }
      for (final sid in toStop) {
        try { await local.removePublishedTrack(sid); } catch (_) {}
      }
    }
    try { await _room?.localParticipant?.setScreenShareEnabled(false); } catch (_) {}
    isScreenSharing = false;
    _currentScreenShareSourceId = null;
    _screenShareTrack = null;
    notifyListeners();
  }

  Future<void> hangUp() async {
    _stopStatsLogging();
    _presenceHeartbeat?.cancel();
    _presenceHeartbeat = null;
    if (_matrixClient != null && _matrixRoomId != null) {
      await _writePresence(false).catchError((_) {});
    }
    _roomEventListener?.dispose();
    _roomEventListener = null;
    _room?.removeListener(_onRoomChanged);
    await _room?.disconnect();
    _room = null;
    _displayName = '';
    isConnecting = false;
    error = null;
    isMuted = false;
    isCameraOff = false;
    isScreenSharing = false;
    isDeafened = false;
    isVoiceChannel = false;
    _mutedBeforeDeafen = false;
    _screenShareTrack = null;
    _currentScreenShareSourceId = null;
    _matrixRoomId = null;
    _matrixClient = null;
    notifyListeners();
  }

  Future<void> changeQuality(String qualityKey) async {
    debugPrint('[LiveKit] Streamer changing publish quality to: $qualityKey');
    _localQualityKey = qualityKey;
    final params = qualityPresets[qualityKey];
    if (params == null) { notifyListeners(); return; }
    if (_room == null) { notifyListeners(); return; }

    final local = _room!.localParticipant;
    if (local == null) { notifyListeners(); return; }

    if (!isCameraOff) {
      await local.setCameraEnabled(false);
      await Future.delayed(const Duration(milliseconds: 400));
      try {
        await local.setCameraEnabled(
          true,
          cameraCaptureOptions: CameraCaptureOptions(params: params),
        );
      } catch (e) {
        debugPrint('[LiveKit] Camera restart error: $e');
        isCameraOff = true;
      }
    }

    if (isScreenSharing && _currentScreenShareSourceId != null) {
      final sid = _currentScreenShareSourceId!;
      await stopScreenShare();
      await Future.delayed(const Duration(milliseconds: 400));
      await startScreenShare(sid);
    }
    notifyListeners();
  }

  Future<void> changeAudioInput(String deviceId) async {
    final participant = _room?.localParticipant;
    if (participant == null) return;
    try {
      final track = await LocalAudioTrack.create(AudioCaptureOptions(deviceId: deviceId));
      for (final pub in List.of(participant.audioTrackPublications)) {
        await participant.removePublishedTrack(pub.sid);
      }
      await participant.publishAudioTrack(track);
      if (isMuted) await participant.setMicrophoneEnabled(false);
    } catch (e) { debugPrint('[Audio] error: $e'); }
  }

  void setRemoteVolume(double volume) {
    if (_room == null) return;
    final mute = volume < 0.05;
    for (final p in _room!.remoteParticipants.values) {
      for (final pub in p.audioTrackPublications) {
        pub.track?.mediaStreamTrack.enabled = !mute;
      }
    }
  }

  void _startStatsLogging() {
    _stopStatsLogging();
    _statsTimer = Timer.periodic(const Duration(seconds: 5), (_) => _logStats());
  }

  void _stopStatsLogging() {
    _statsTimer?.cancel();
    _statsTimer = null;
  }

  Future<void> _logStats() async {
    if (_room == null) return;
    // Basic stats logging...
    debugPrint('[Stats] SubQuality: $_subscribeQualityKey | PubQuality: $_localQualityKey');
  }
}
