import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';

import 'package:asr_client/models/conversation.dart';
import 'package:asr_client/models/websocket_message.dart';
import 'package:asr_client/providers/livekit_providers.dart';
import 'package:asr_client/services/livekit_service.dart';
import 'package:asr_client/providers/settings_providers.dart';

enum RecordingStatus { idle, recording, paused, ending }

@immutable
final class _Unset {
  const _Unset();
}
const _unset = _Unset();

final class RecordingState {
  const RecordingState({
    this.status = RecordingStatus.idle,
    this.elapsed = Duration.zero,
    this.audioLevel = 0.0,
    this.items = const [],
    this.liveUtterance,
    this.liveReply,
    this.isConnected = false,
    this.errorMessage,
    this.sessionId,
    this.projectName,
    this.taskName,
    this.templateName,
  });

  final RecordingStatus status;
  final Duration elapsed;
  final double audioLevel;
  final List<ConversationItem> items;
  final String? liveUtterance;
  final String? liveReply;
  final bool isConnected;
  final String? errorMessage;
  final String? sessionId;
  final String? projectName;
  final String? taskName;
  final String? templateName;

  RecordingState copyWith({
    RecordingStatus? status,
    Duration? elapsed,
    double? audioLevel,
    List<ConversationItem>? items,
    Object? liveUtterance = _unset,
    Object? liveReply = _unset,
    bool? isConnected,
    Object? errorMessage = _unset,
    Object? sessionId = _unset,
    Object? projectName = _unset,
    Object? taskName = _unset,
    Object? templateName = _unset,
  }) {
    return RecordingState(
      status: status ?? this.status,
      elapsed: elapsed ?? this.elapsed,
      audioLevel: audioLevel ?? this.audioLevel,
      items: items ?? this.items,
      liveUtterance: identical(liveUtterance, _unset)
          ? this.liveUtterance
          : liveUtterance as String?,
      liveReply: identical(liveReply, _unset)
          ? this.liveReply
          : liveReply as String?,
      isConnected: isConnected ?? this.isConnected,
      errorMessage: identical(errorMessage, _unset)
          ? this.errorMessage
          : errorMessage as String?,
      sessionId: identical(sessionId, _unset)
          ? this.sessionId
          : sessionId as String?,
      projectName: identical(projectName, _unset)
          ? this.projectName
          : projectName as String?,
      taskName: identical(taskName, _unset)
          ? this.taskName
          : taskName as String?,
      templateName: identical(templateName, _unset)
          ? this.templateName
          : templateName as String?,
    );
  }

  bool get isRecording => status == RecordingStatus.recording;
  bool get isPaused => status == RecordingStatus.paused;
}

final recordingNotifierProvider =
    AutoDisposeAsyncNotifierProvider<RecordingNotifier, RecordingState>(
      RecordingNotifier.new,
    );

final class RecordingNotifier extends AutoDisposeAsyncNotifier<RecordingState> {
  Timer? _timer;
  final _uuid = const Uuid();
  DateTime? _startTime;
  Duration _accumulated = Duration.zero;
  StreamSubscription<ServerMessage>? _messageSub;

  LiveKitService get _liveKit => ref.read(liveKitServiceProvider);

  @override
  FutureOr<RecordingState> build() async {
    ref.onDispose(() {
      _timer?.cancel();
      _messageSub?.cancel();
    });

    final roomName = 'asr-test';
    final identity = 'user-${_uuid.v4().substring(0, 6)}';
    final lkUrl = await ref.read(settingsServiceProvider).getLiveKitUrl();

    final initialState = const RecordingState(
      projectName: 'G15 沈海高速改扩建 · K1120+300',
      taskName: '混凝土坍落度试验',
      templateName: '模板 · 普通混凝土坍落度 GB/T 50080',
    );

    try {
      await _liveKit.connect(url: lkUrl, room: roomName, identity: identity);
    } on Exception catch (e) {
      return initialState.copyWith(
        errorMessage: '无法连接 LiveKit: $e',
      );
    }

    _listenToMessages();
    return _startedState(initialState);
  }

  RecordingState _startedState(RecordingState s) {
    _startTime = DateTime.now();
    _timer = Timer.periodic(const Duration(seconds: 1), (_) {
      final now = DateTime.now();
      final elapsed = _accumulated +
          (_startTime != null ? now.difference(_startTime!) : Duration.zero);
      _updateState((current) => current.copyWith(elapsed: elapsed));
    });
    return s.copyWith(status: RecordingStatus.recording, isConnected: true);
  }

  void _listenToMessages() {
    _messageSub = _liveKit.messageStream.listen((message) {
      debugPrint('[notifier] msg: ${message.runtimeType}');
      switch (message) {
        case PartialMessage(:final text):
          _updateState((s) => s.copyWith(liveUtterance: text));
        case UtteranceMessage(:final text):
          _finalizeBubble(text);
        case ReplyMessage(:final text):
          _updateState((s) => s.copyWith(liveReply: null));
          _addAiReply(text);
        case ReplyPartialMessage(:final text):
          _updateState((s) => s.copyWith(liveReply: text));
        case ErrorMessage(:final message):
          _updateState((s) => s.copyWith(errorMessage: message));
        case ClearedMessage():
          break;
        case ConnectedMessage():
          _updateState((s) => s.copyWith(isConnected: true));
      }
    }, onError: (_) {});
  }

  void _finalizeBubble(String text) {
    if (text.trim().isEmpty) return;
    final item = ConversationItem(
      id: _uuid.v4(),
      type: ConversationItemType.userBubble,
      timestamp: DateTime.now(),
      text: text,
    );
    _updateState((s) => s.copyWith(
      items: [...s.items, item],
      liveUtterance: null,
    ));
  }

  void _addAiReply(String text) {
    final item = ConversationItem(
      id: _uuid.v4(),
      type: ConversationItemType.aiCard,
      timestamp: DateTime.now(),
      text: text,
    );
    _updateState((s) => s.copyWith(items: [...s.items, item]));
  }

  void pauseRecording() {
    if (state.value?.status != RecordingStatus.recording) return;
    _accumulated = state.value!.elapsed;
    _timer?.cancel();
    // Finalize any pending live utterance before disconnecting.
    final live = state.value?.liveUtterance?.trim();
    if (live != null && live.isNotEmpty) {
      _finalizeBubble(live);
    }
    _liveKit.disconnect();
    _updateState((s) => s.copyWith(
      status: RecordingStatus.paused,
      liveUtterance: null,
    ));
  }

  Future<void> resumeRecording() async {
    if (state.value?.status != RecordingStatus.paused) return;
    try {
      final roomName = 'asr-test';
      final lkUrl = await ref.read(settingsServiceProvider).getLiveKitUrl();
      await _liveKit.connect(url: lkUrl, room: roomName, identity: 'user-${_uuid.v4().substring(0, 6)}');
      _updateState((s) => _startedState(s));
    } on Exception catch (e) {
      _updateState((s) => s.copyWith(errorMessage: '无法恢复录音: $e'));
    }
  }

  void endRecording() {
    _updateState((s) => s.copyWith(status: RecordingStatus.ending));
  }

  Future<void> confirmEnd() async {
    _timer?.cancel();
    _messageSub?.cancel();
    await _liveKit.disconnect();
    _updateState((s) => s.copyWith(
      status: RecordingStatus.idle,
      elapsed: Duration.zero,
      items: [],
      liveUtterance: null,
      isConnected: false,
    ));
  }

  void cancelEnd() {
    _updateState((s) => s.copyWith(
      status: s.isPaused ? RecordingStatus.paused : RecordingStatus.recording,
    ));
  }

  void clearError() {
    _updateState((s) => s.copyWith(errorMessage: null));
  }

  void _updateState(RecordingState Function(RecordingState) update) {
    state = AsyncData(update(state.value ?? const RecordingState()));
  }
}
