import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../api/api_client_provider.dart';
import 'active_session_notifier.dart';

part 'session_closure_notifier.g.dart';

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

class ClosureInitialData extends SessionClosureData {
  const ClosureInitialData();
}

class ExtendingWaitingMitraData extends SessionClosureData {
  const ExtendingWaitingMitraData();
}

class ClosureShowGoodbyeData extends SessionClosureData {
  const ClosureShowGoodbyeData();
}

class ClosureSubmittingData extends SessionClosureData {
  const ClosureSubmittingData();
}

class ClosureCompleteData extends SessionClosureData {
  const ClosureCompleteData();
}

/// Stage 7 — emitted when the close-session API returns 409 (mitra-rejects-
/// close path). The chat screen surfaces a "bestie offline / returning"
/// fallback popup; Stage 8 will own the proper variant.
class ClosureRejectedByMitraData extends SessionClosureData {
  const ClosureRejectedByMitraData();
}

class ClosureErrorData extends SessionClosureData {
  final String message;
  const ClosureErrorData(this.message);
}

@Riverpod(keepAlive: true)
class SessionClosure extends _$SessionClosure {
  @override
  SessionClosureData build() => const ClosureInitialData();

  /// Extension request is a 3-step flow with the extension cost held in its
  /// own `payment_session` (never combined with a free trial). Server-side,
  /// the extension service refuses requests without an
  /// `extension_payment_session_id` on a confirmed, is_extension payment session.
  ///
  /// 1. POST `/api/client/payment-sessions` with `is_extension: true`
  /// 2. POST `/api/client/payment-sessions/:id/confirm`
  /// 3. POST `/api/client/chat/session/:sessionId/extend` with the
  ///    extension_payment_session_id from step 2.
  ///
  /// Charge timing is server-side: only on actual approve / auto-approve.
  /// If the mitra explicitly rejects within 10s the payment is failed back, no charge.
  Future<void> requestExtension(String sessionId, {required int durationMinutes, required int price}) async {
    state = const ExtendingWaitingMitraData();
    try {
      final api = ref.read(apiClientProvider);

      final createResp = await api.post('/api/client/payment-sessions/', data: {
        'duration_minutes': durationMinutes,
        'is_extension': true,
      });
      final paymentSessionId = (createResp['data'] as Map<String, dynamic>)['id'] as String;

      // Backend rejects truly empty bodies on confirm, so always send `{}`.
      await api.post(
        '/api/client/payment-sessions/$paymentSessionId/confirm',
        data: const <String, dynamic>{},
      );

      // Trigger the extension request. The actual approve/reject round-trip is
      // owned by the chat WS — ChatNotifier surfaces it.
      await api.post('/api/client/chat/session/$sessionId/extend', data: {
        'duration_minutes': durationMinutes,
        'price': price,
        'extension_payment_session_id': paymentSessionId,
      });
    } catch (e) {
      state = const ClosureErrorData('Gagal meminta perpanjangan.');
    }
  }

  void declineExtension() {
    state = const ClosureShowGoodbyeData();
  }

  void reset() {
    state = const ClosureInitialData();
  }

  Future<void> submitGoodbye(String sessionId, String message) async {
    state = const ClosureSubmittingData();
    try {
      await ref.read(apiClientProvider).post('/api/shared/sessions/$sessionId/close-message', data: {
        'message': message,
      });
      state = const ClosureCompleteData();
      // The session is now completed server-side; refresh home snapshot so the
      // CTA returns to "Mulai Curhat" without waiting for the next poll tick.
      ref.invalidate(activeSessionProvider);
    } on DioException catch (e) {
      final code = e.response?.data?['error']?['code'];
      if (code == 'SESSION_NOT_ACTIVE' || e.response?.statusCode == 409) {
        state = const ClosureCompleteData();
        ref.invalidate(activeSessionProvider);
      } else {
        state = const ClosureErrorData('Gagal mengirim pesan penutup.');
      }
    } catch (e) {
      state = const ClosureErrorData('Gagal mengirim pesan penutup.');
    }
  }

  /// Stage 7 — customer-initiated close. Calls
  /// `POST /api/client/session/:sessionId/end`. On success, emits
  /// `ClosureCompleteData` and refreshes the active-session snapshot so the
  /// home CTA flips back to "Mulai Curhat" without waiting for the next poll.
  /// On 409, emits `ClosureRejectedByMitraData` so the chat screen can show
  /// the bestie-returning fallback popup. Other errors fall back to
  /// `ClosureErrorData`.
  Future<void> closeSession(String sessionId) async {
    try {
      await ref.read(apiClientProvider).post(
        '/api/client/session/$sessionId/end',
        data: const <String, dynamic>{},
      );
      state = const ClosureCompleteData();
      ref.invalidate(activeSessionProvider);
    } on DioException catch (e) {
      if (e.response?.statusCode == 409) {
        state = const ClosureRejectedByMitraData();
      } else {
        final code = e.response?.data?['error']?['code'];
        if (code == 'SESSION_NOT_ACTIVE') {
          // Server treats it as already closed — equivalent to success.
          state = const ClosureCompleteData();
          ref.invalidate(activeSessionProvider);
        } else {
          state = const ClosureErrorData('Gagal mengakhiri sesi.');
        }
      }
    } catch (_) {
      state = const ClosureErrorData('Gagal mengakhiri sesi.');
    }
  }
}
