import 'dart:async';
import '../../../../core/network/websocket_client.dart';
import '../../domain/entities/chat_message.dart';
import '../../domain/entities/stream_event.dart';
import '../../domain/repositories/chat_repository.dart';
import '../datasources/chat_local_datasource.dart';
import '../datasources/chat_remote_datasource.dart';
import '../models/stream_event_model.dart';

class ChatRepositoryImpl implements ChatRepository {
  final ChatRemoteDatasource _remoteDatasource;
  final ChatLocalDatasource _localDatasource;
  final WebSocketClient _webSocketClient;
  final Future<String?> Function() _getAccessToken;

  ChatRepositoryImpl({
    required ChatRemoteDatasource remoteDatasource,
    required ChatLocalDatasource localDatasource,
    required WebSocketClient webSocketClient,
    required Future<String?> Function() getAccessToken,
  })  : _remoteDatasource = remoteDatasource,
        _localDatasource = localDatasource,
        _webSocketClient = webSocketClient,
        _getAccessToken = getAccessToken;

  @override
  Stream<StreamEvent> sendMessage({
    required String sessionId,
    required String message,
    List<Map<String, dynamic>>? attachments,
  }) async* {
    // Create the task on the backend
    final response = await _remoteDatasource.createTask(
      sessionId: sessionId,
      message: message,
      attachments: attachments,
    );

    final returnedSessionId = response['sessionId'] as String? ??
        response['session_id'] as String? ??
        sessionId;
    final taskId = response['taskId'] as String? ?? response['task_id'] as String?;

    // Emit the real sessionId and taskId so the notifier can capture them
    yield SessionInfoEvent(returnedSessionId);
    if (taskId != null) yield TaskInfoEvent(taskId);

    // Connect to the agent WebSocket and subscribe to the session
    final token = await _getAccessToken();
    await _webSocketClient.connect('/ws/agent', token: token);
    _webSocketClient.send({
      'event': 'subscribe_session',
      'data': {'sessionId': returnedSessionId, 'taskId': taskId},
    });

    // Yield events from the WebSocket stream
    yield* _webSocketClient.messages.transform(
      StreamTransformer<Map<String, dynamic>, StreamEvent>.fromHandlers(
        handleData: (msg, sink) {
          final event = msg['event'] as String? ?? msg['type'] as String? ?? '';

          if (event == 'stream_event' || event == 'message') {
            final data = msg['data'] as Map<String, dynamic>? ?? msg;
            final model = StreamEventModel.fromJson(data);
            sink.add(model.toEntity());
          } else if (event == 'stream_end' || event == 'done' || event == 'complete') {
            final summary = msg['summary'] as String? ?? '';
            sink.add(CompletedEvent(summary));
            sink.close();
          } else if (event == 'error') {
            final message = msg['message'] as String? ?? '流式传输错误';
            sink.add(ErrorEvent(message));
            sink.close();
          }
        },
      ),
    );
  }

  @override
  Stream<StreamEvent> sendVoiceMessage({
    required String sessionId,
    required String audioPath,
  }) async* {
    // Kept for interface compatibility — ChatNotifier calls sendVoiceMessage
    // on the datasource directly (to get the transcript), then calls
    // subscribeExistingTask. This method is not used.
    yield* subscribeExistingTask(sessionId: sessionId, taskId: '');
  }

  @override
  Stream<StreamEvent> subscribeExistingTask({
    required String sessionId,
    required String taskId,
  }) async* {
    final token = await _getAccessToken();
    await _webSocketClient.connect('/ws/agent', token: token);
    _webSocketClient.send({
      'event': 'subscribe_session',
      'data': {'sessionId': sessionId, 'taskId': taskId},
    });

    yield* _webSocketClient.messages.transform(
      StreamTransformer<Map<String, dynamic>, StreamEvent>.fromHandlers(
        handleData: (msg, sink) {
          final event = msg['event'] as String? ?? msg['type'] as String? ?? '';

          if (event == 'stream_event' || event == 'message') {
            final data = msg['data'] as Map<String, dynamic>? ?? msg;
            final model = StreamEventModel.fromJson(data);
            sink.add(model.toEntity());
          } else if (event == 'stream_end' || event == 'done' || event == 'complete') {
            sink.add(CompletedEvent(msg['summary'] as String? ?? ''));
            sink.close();
          } else if (event == 'error') {
            sink.add(ErrorEvent(msg['message'] as String? ?? '流式传输错误'));
            sink.close();
          }
        },
      ),
    );
  }

  @override
  Future<List<ChatMessage>> getSessionHistory(String sessionId) async {
    try {
      // Try remote first
      final models = await _remoteDatasource.getSessionHistory(sessionId);

      // Cache for offline access
      await _localDatasource.cacheMessages(sessionId, models);

      return models.map((m) => m.toEntity()).toList();
    } catch (_) {
      // Fall back to local cache on network failure
      final cached = _localDatasource.getCachedMessages(sessionId);
      return cached.map((m) => m.toEntity()).toList();
    }
  }

  @override
  Future<void> approveCommand(String taskId) async {
    await _remoteDatasource.approveCommand(taskId);
  }

  @override
  Future<void> rejectCommand(String taskId, {String? reason}) async {
    await _remoteDatasource.rejectCommand(taskId, reason: reason);
  }

  @override
  Future<void> cancelTask(String taskId) async {
    await _remoteDatasource.cancelTask(taskId);
  }

  @override
  Stream<StreamEvent> injectMessage({
    required String taskId,
    required String message,
  }) async* {
    // Call inject API — this cancels the current task and starts a new one
    final response = await _remoteDatasource.injectMessage(
      taskId: taskId,
      message: message,
    );

    final newTaskId = response['taskId'] as String? ?? response['task_id'] as String?;
    if (newTaskId != null) {
      yield TaskInfoEvent(newTaskId);
    }

    // The WebSocket is already connected and subscribed to this session.
    // New events from the injected task will flow through the existing connection.
    yield* _webSocketClient.messages.transform(
      StreamTransformer<Map<String, dynamic>, StreamEvent>.fromHandlers(
        handleData: (msg, sink) {
          final event = msg['event'] as String? ?? msg['type'] as String? ?? '';

          if (event == 'stream_event' || event == 'message') {
            final data = msg['data'] as Map<String, dynamic>? ?? msg;
            final model = StreamEventModel.fromJson(data);
            final entity = model.toEntity();
            // Skip cancelled events from the old task during inject
            if (entity is CancelledEvent) return;
            sink.add(entity);
          } else if (event == 'stream_end' || event == 'done' || event == 'complete') {
            final summary = msg['summary'] as String? ?? '';
            sink.add(CompletedEvent(summary));
            sink.close();
          } else if (event == 'error') {
            final message = msg['message'] as String? ?? '流式传输错误';
            sink.add(ErrorEvent(message));
            sink.close();
          }
        },
      ),
    );
  }

  @override
  Future<void> confirmStandingOrder(
    String sessionId,
    Map<String, dynamic> draft,
  ) async {
    await _remoteDatasource.confirmStandingOrder(sessionId, draft);
  }
}
