import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../api/api_client.dart';
import '../api/api_client_provider.dart';
import '../auth/auth_bridge.dart';
import '../constants.dart';
import '../notifications/notification_service.dart';

part 'chat_request_notifier.g.dart';

// Stale reason for dismissed requests
enum StaleReason {
  cancelledByCustomer, // "Permintaan dibatalkan oleh customer"
  acceptedByOther, // "Permintaan diterima oleh Bestie lain"
  expired, // "Permintaan kedaluwarsa"
}

// States
sealed class ChatRequestData {
  const ChatRequestData();
}

class ChatRequestIdleData extends ChatRequestData {
  const ChatRequestIdleData();
}

class ChatRequestListeningData extends ChatRequestData {
  const ChatRequestListeningData();
}

class ChatRequestIncomingData extends ChatRequestData {
  final String sessionId;
  final int? durationMinutes;
  final bool? isFreeTrial;
  final TopicSensitivity topicSensitivity;
  final DateTime? createdAt;
  const ChatRequestIncomingData(
    this.sessionId, {
    this.durationMinutes,
    this.isFreeTrial,
    this.topicSensitivity = TopicSensitivity.regular,
    this.createdAt,
  });
}

class ChatRequestStaleData extends ChatRequestData {
  final String sessionId;
  final StaleReason reason;
  const ChatRequestStaleData(this.sessionId, this.reason);
}

class ChatRequestAcceptingData extends ChatRequestData {
  const ChatRequestAcceptingData();
}

class ChatRequestAcceptedData extends ChatRequestData {
  final Map<String, dynamic> session;
  const ChatRequestAcceptedData(this.session);
}

class ChatRequestErrorData extends ChatRequestData {
  final String message;
  const ChatRequestErrorData(this.message);
}

@Riverpod(keepAlive: true)
class ChatRequest extends _$ChatRequest {
  WebSocketChannel? _channel;
  StreamSubscription? _wsSubscription;
  final List<Map<String, dynamic>> _pendingQueue = [];

  ApiClient get _apiClient => ref.read(apiClientProvider);

  /// Number of active requests (currently displayed + queued).
  int get activeRequestCount {
    final current = state is ChatRequestIncomingData ? 1 : 0;
    return current + _pendingQueue.length;
  }

  @override
  ChatRequestData build() => const ChatRequestIdleData();

  /// Fetch pending requests from backend and populate the queue.
  Future<void> loadPendingRequests() async {
    try {
      final response = await _apiClient.get('/api/mitra/chat-requests/pending');
      final requests = response['data'] as List<dynamic>;
      if (requests.isEmpty) return;

      for (final r in requests) {
        final sessionId = r['session_id'] as String;
        // Skip if already showing or queued
        if (state is ChatRequestIncomingData &&
            (state as ChatRequestIncomingData).sessionId == sessionId) continue;
        if (_pendingQueue.any((q) => q['session_id'] == sessionId)) continue;

        final data = {
          'session_id': sessionId,
          'duration_minutes': r['duration_minutes'],
          'is_free_trial': r['is_free_trial'],
          'topic_sensitivity': r['topic_sensitivity'],
          'created_at': r['created_at'],
        };

        if (state is ChatRequestIncomingData ||
            state is ChatRequestStaleData ||
            state is ChatRequestAcceptingData) {
          _pendingQueue.add(data);
        } else {
          state = ChatRequestIncomingData(
            sessionId,
            durationMinutes: r['duration_minutes'] as int?,
            isFreeTrial: r['is_free_trial'] as bool?,
            topicSensitivity: TopicSensitivity.fromString(r['topic_sensitivity'] as String?),
            createdAt: r['created_at'] != null
                ? DateTime.tryParse(r['created_at'] as String)
                : null,
          );
        }
      }
    } catch (_) {}
  }

  Future<void> startListening() async {
    // Don't reset state if showing a request, stale message, or actively accepting
    if (state is ChatRequestIncomingData ||
        state is ChatRequestStaleData ||
        state is ChatRequestAcceptingData ||
        state is ChatRequestAcceptedData) {
      // Still reconnect WebSocket if needed, but don't change state
      if (_channel == null) await _connectWebSocket();
      return;
    }
    _closeWebSocket();
    state = const ChatRequestListeningData();
    await _connectWebSocket();
  }

  void stopListening() {
    _closeWebSocket();
    _pendingQueue.clear();
    state = const ChatRequestIdleData();
  }

  Future<void> _connectWebSocket() async {
    try {
      final token = ref.read(authBridgeProvider).accessToken;
      if (token == null) return;

      final wsUrl = ApiClient.baseUrl
          .replaceFirst('https://', 'wss://')
          .replaceFirst('http://', 'ws://');

      _channel = WebSocketChannel.connect(Uri.parse('$wsUrl/api/shared/ws'));

      _wsSubscription = _channel!.stream.listen(
        (raw) {
          try {
            final text = raw is String ? raw : String.fromCharCodes(raw as List<int>);
            final data = jsonDecode(text) as Map<String, dynamic>;
            if (data['type'] == WsMessage.authOk) return;
            _onRequestReceived(data);
          } catch (_) {}
        },
        onError: (_) => _onConnectionError(),
        onDone: () => _onConnectionError(),
      );

      _channel!.sink.add(jsonEncode({
        'type': WsMessage.auth,
        'token': token,
      }));
    } catch (_) {
      _onConnectionError();
    }
  }

  void _onConnectionError() {
    _closeWebSocket();
    if (state is! ChatRequestIdleData) {
      state = const ChatRequestListeningData();
    }
  }

  void _onRequestReceived(Map<String, dynamic> data) {
    final type = data['type'] as String?;

    if (type == WsMessage.chatRequest) {
      final sessionId = data['session_id'] as String;

      // If already showing a request or stale message, queue it
      if (state is ChatRequestIncomingData ||
          state is ChatRequestStaleData ||
          state is ChatRequestAcceptingData) {
        if (!_pendingQueue.any((q) => q['session_id'] == sessionId)) {
          _pendingQueue.add(data);
        }
      } else {
        state = ChatRequestIncomingData(
          sessionId,
          durationMinutes: data['duration_minutes'] as int?,
          isFreeTrial: data['is_free_trial'] as bool?,
          topicSensitivity: TopicSensitivity.fromString(data['topic_sensitivity'] as String?),
          createdAt: data['created_at'] != null
              ? DateTime.tryParse(data['created_at'] as String)
              : null,
        );
      }

      // Show local notification
      NotificationService.showLocalNotification(
        title: 'Permintaan Chat Baru',
        body: 'Ada pelanggan yang ingin curhat! Ketuk untuk menerima.',
        data: {'type': 'chat_request', 'session_id': sessionId, 'action': 'open_accept'},
      );
    } else if (type == WsMessage.chatRequestClosed) {
      final closedSessionId = data['session_id'] as String;
      final reason = data['reason'] as String?;

      // Remove from queue if queued
      _pendingQueue.removeWhere((q) => q['session_id'] == closedSessionId);

      // If currently displayed, transition to stale
      if (state is ChatRequestIncomingData &&
          (state as ChatRequestIncomingData).sessionId == closedSessionId) {
        final staleReason = switch (reason) {
          'cancelled_by_customer' => StaleReason.cancelledByCustomer,
          'accepted_by_other' => StaleReason.acceptedByOther,
          'expired' => StaleReason.expired,
          _ => StaleReason.expired,
        };
        state = ChatRequestStaleData(closedSessionId, staleReason);
      }
    } else if (type == 'session_rerouted') {
      _pendingQueue.clear();
      state = const ChatRequestListeningData();
    } else if (type == 'session_assigned') {
      _pendingQueue.clear();
      state = ChatRequestAcceptedData({'session_id': data['session_id']});
    }
  }

  /// Called when user taps a chat_request notification.
  Future<void> setIncomingFromNotification(String sessionId) async {
    state = ChatRequestIncomingData(sessionId);
    await validateIncomingRequest();
  }

  /// Check if the current incoming request is still valid.
  Future<void> validateIncomingRequest() async {
    if (state is! ChatRequestIncomingData) return;
    final sessionId = (state as ChatRequestIncomingData).sessionId;
    try {
      final response = await _apiClient.get('/api/mitra/chat-requests/$sessionId/status');
      final status = response['data']?['status'] as String?;
      if (status != 'pending_acceptance') {
        state = ChatRequestStaleData(sessionId, StaleReason.expired);
      }
    } catch (_) {
      // On error, keep current state
    }
  }

  /// Swipe down on active request — ignore without sending reject to backend.
  void ignore() {
    _advanceQueue();
  }

  /// Acknowledge a stale message (OK button or swipe down).
  void acknowledgeStale() {
    _advanceQueue();
  }

  /// Show next queued request or return to listening.
  void _advanceQueue() {
    if (_pendingQueue.isNotEmpty) {
      final next = _pendingQueue.removeAt(0);
      final sessionId = next['session_id'] as String;
      state = ChatRequestIncomingData(
        sessionId,
        durationMinutes: next['duration_minutes'] as int?,
        isFreeTrial: next['is_free_trial'] as bool?,
        topicSensitivity: TopicSensitivity.fromString(next['topic_sensitivity'] as String?),
        createdAt: next['created_at'] != null
            ? DateTime.tryParse(next['created_at'] as String)
            : null,
      );
      validateIncomingRequest();
    } else {
      state = const ChatRequestListeningData();
    }
  }

  Future<void> accept(String sessionId) async {
    state = const ChatRequestAcceptingData();
    try {
      final response = await _apiClient.post('/api/mitra/chat-requests/$sessionId/accept');
      _pendingQueue.clear();
      state = ChatRequestAcceptedData(response['data'] as Map<String, dynamic>);
    } on DioException catch (e) {
      final code = e.response?.data?['error']?['code'];
      if (code == 'REQUEST_UNAVAILABLE') {
        state = ChatRequestStaleData(sessionId, StaleReason.acceptedByOther);
      } else {
        state = const ChatRequestErrorData('Gagal menerima. Coba lagi.');
      }
    }
  }

  Future<void> decline(String sessionId) async {
    try {
      await _apiClient.post('/api/mitra/chat-requests/$sessionId/decline');
    } catch (_) {}
    _advanceQueue();
  }

  void _closeWebSocket() {
    _wsSubscription?.cancel();
    _wsSubscription = null;
    _channel?.sink.close();
    _channel = null;
  }
}
