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 '../chat/active_session_notifier.dart';
import '../constants.dart';

part 'pairing_notifier.g.dart';

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

class PairingInitialData extends PairingData {
  const PairingInitialData();
}

class PairingSearchingData extends PairingData {
  final String sessionId;
  const PairingSearchingData(this.sessionId);
}

class PairingBestieFoundData extends PairingData {
  final String sessionId;
  final String mitraName;
  const PairingBestieFoundData({required this.sessionId, required this.mitraName});
}

class PairingActiveData extends PairingData {
  final String sessionId;
  final String mitraName;
  const PairingActiveData({required this.sessionId, required this.mitraName});
}

class PairingNoBestieData extends PairingData {
  const PairingNoBestieData();
}

class PairingCancelledData extends PairingData {
  const PairingCancelledData();
}

class PairingErrorData extends PairingData {
  final String message;
  const PairingErrorData(this.message);
}

@Riverpod(keepAlive: true)
class Pairing extends _$Pairing {
  Timer? _timeoutTimer;
  WebSocketChannel? _channel;
  StreamSubscription? _wsSubscription;

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

  @override
  PairingData build() => const PairingInitialData();

  Future<void> requestPairing({required TopicSensitivity topicSensitivity}) async {
    await _doPairingRequest({'topic_sensitivity': topicSensitivity.value});
  }

  Future<void> requestPairingWithTier({
    int? durationMinutes,
    int? price,
    bool isFreeTrial = false,
    required TopicSensitivity topicSensitivity,
  }) async {
    final body = <String, dynamic>{'topic_sensitivity': topicSensitivity.value};
    if (isFreeTrial) {
      body['is_free_trial'] = true;
    } else {
      body['duration_minutes'] = durationMinutes;
      body['price'] = price;
    }
    await _doPairingRequest(body);
  }

  Future<void> _doPairingRequest(Map<String, dynamic> body) async {
    if (state is! PairingInitialData) {
      state = const PairingInitialData();
    }
    try {
      await _connectWebSocket();

      final response = await _apiClient.post('/api/client/chat/request', data: body);
      final data = response['data'] as Map<String, dynamic>;
      final sessionId = data['id'] as String;

      state = PairingSearchingData(sessionId);

      _timeoutTimer = Timer(const Duration(seconds: 60), () {
        _cleanup();
        state = const PairingNoBestieData();
      });
    } on DioException catch (e) {
      _cleanup();
      final code = e.response?.data?['error']?['code'];
      if (code == 'NO_MITRA_AVAILABLE') {
        state = const PairingNoBestieData();
      } else if (code == 'ALREADY_ACTIVE') {
        state = const PairingErrorData('Kamu sudah memiliki sesi aktif.');
      } else if (code == 'FREE_TRIAL_INELIGIBLE') {
        state = const PairingErrorData('Kamu tidak memenuhi syarat untuk free trial.');
      } else {
        state = const PairingErrorData('Gagal memulai. Coba lagi.');
      }
    }
  }

  Future<void> _connectWebSocket() async {
    _closeWebSocket();
    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) {
        final data = jsonDecode(raw as String) as Map<String, dynamic>;
        if (data['type'] == WsMessage.authOk) return;
        _onStatusUpdate(data);
      },
      onError: (_) {},
      onDone: () {},
    );

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

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

    if (type == WsMessage.paired) {
      _cleanup();
      final mitraName = data['mitra_display_name'] as String? ?? 'Bestie';
      final sessionId = data['session_id'] as String;
      state = PairingBestieFoundData(sessionId: sessionId, mitraName: mitraName);

      // A session now exists for this customer — refresh the shared snapshot
      // so the home CTA reflects it immediately when the user returns.
      // ignore: unawaited_futures
      ref.read(activeSessionProvider.notifier).refresh();

      await Future.delayed(const Duration(seconds: 2));
      state = PairingActiveData(sessionId: sessionId, mitraName: mitraName);
    } else if (type == SessionStatus.expired) {
      _cleanup();
      state = const PairingNoBestieData();
    }
  }

  Future<void> cancelPairing() async {
    if (state is PairingSearchingData) {
      final sessionId = (state as PairingSearchingData).sessionId;
      try {
        await _apiClient.post('/api/client/chat/request/$sessionId/cancel');
      } catch (_) {}
      _cleanup();
      state = const PairingCancelledData();
    }
  }

  void reset() {
    _cleanup();
    state = const PairingInitialData();
  }

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

  void _cleanup() {
    _timeoutTimer?.cancel();
    _timeoutTimer = null;
    _closeWebSocket();
  }
}
