import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../core/api/api_client.dart';
import '../../core/api/api_client_provider.dart';
import '../../core/constants.dart';

part 'payment_notifier.g.dart';

/// Payment-session lifecycle, customer side. The screen owns one of these per
/// (mitra-target, duration) attempt; the notifier wraps the REST calls to
/// `/api/client/payment-sessions`.
sealed class PaymentSessionData {
  const PaymentSessionData();
}

class PaymentInitialData extends PaymentSessionData {
  const PaymentInitialData();
}

class PaymentCreatingData extends PaymentSessionData {
  const PaymentCreatingData();
}

/// Created server-side, sitting in `pending` until the customer taps "Bayar".
class PaymentPendingData extends PaymentSessionData {
  final String paymentSessionId;
  final int amount;
  final int durationMinutes;
  final bool isFreeTrial;
  final bool isExtension;
  final String? targetedMitraId;

  const PaymentPendingData({
    required this.paymentSessionId,
    required this.amount,
    required this.durationMinutes,
    required this.isFreeTrial,
    required this.isExtension,
    this.targetedMitraId,
  });
}

class PaymentConfirmingData extends PaymentSessionData {
  final String paymentSessionId;
  const PaymentConfirmingData(this.paymentSessionId);
}

/// Confirmed; the customer can now be routed to the searching screen with
/// this `paymentSessionId` (and optional `targetedMitraId` for "Curhat lagi").
class PaymentConfirmedData extends PaymentSessionData {
  final String paymentSessionId;
  final int durationMinutes;
  final bool isFreeTrial;
  final bool isExtension;
  final String? targetedMitraId;

  const PaymentConfirmedData({
    required this.paymentSessionId,
    required this.durationMinutes,
    required this.isFreeTrial,
    required this.isExtension,
    this.targetedMitraId,
  });
}

class PaymentErrorData extends PaymentSessionData {
  final String message;
  const PaymentErrorData(this.message);
}

@riverpod
class Payment extends _$Payment {
  ApiClient get _api => ref.read(apiClientProvider);

  @override
  PaymentSessionData build() => const PaymentInitialData();

  /// Create a `pending` payment session for the chosen [durationMinutes].
  /// Pass [targetedMitraId] for the "Curhat lagi" path; pass [isExtension]
  /// for an extension-cost payment (never combined with free trial).
  Future<void> createSession({
    required int durationMinutes,
    String? targetedMitraId,
    bool isExtension = false,
  }) async {
    state = const PaymentCreatingData();
    try {
      final body = <String, dynamic>{
        'duration_minutes': durationMinutes,
        if (targetedMitraId != null) 'targeted_mitra_id': targetedMitraId,
        if (isExtension) 'is_extension': true,
      };
      // Trailing slash matters: the backend route is `app.post('/', ...)` mounted
      // at prefix `/api/client/payment-sessions`, and Fastify is not configured
      // with `ignoreTrailingSlash: true`, so the canonical URL has the slash.
      final response = await _api.post('/api/client/payment-sessions/', data: body);
      final data = response['data'] as Map<String, dynamic>;
      state = PaymentPendingData(
        paymentSessionId: data['id'] as String,
        amount: data['amount'] as int? ?? 0,
        durationMinutes: data['duration_minutes'] as int? ?? durationMinutes,
        isFreeTrial: data['is_free_trial'] as bool? ?? false,
        isExtension: data['is_extension'] as bool? ?? isExtension,
        targetedMitraId: data['targeted_mitra_id'] as String?,
      );
    } on DioException catch (e) {
      state = PaymentErrorData(_humanError(e, fallback: 'Gagal membuat sesi pembayaran.'));
    } catch (_) {
      state = const PaymentErrorData('Gagal membuat sesi pembayaran.');
    }
  }

  /// Confirm the pending payment. Backend rejects truly empty bodies on
  /// `POST .../confirm`, so we always send `{}`.
  Future<void> confirm() async {
    final current = state;
    if (current is! PaymentPendingData) return;
    state = PaymentConfirmingData(current.paymentSessionId);
    try {
      await _api.post(
        '/api/client/payment-sessions/${current.paymentSessionId}/confirm',
        data: const <String, dynamic>{},
      );
      state = PaymentConfirmedData(
        paymentSessionId: current.paymentSessionId,
        durationMinutes: current.durationMinutes,
        isFreeTrial: current.isFreeTrial,
        isExtension: current.isExtension,
        targetedMitraId: current.targetedMitraId,
      );
    } on DioException catch (e) {
      state = PaymentErrorData(_humanError(e, fallback: 'Gagal mengkonfirmasi pembayaran.'));
    } catch (_) {
      state = const PaymentErrorData('Gagal mengkonfirmasi pembayaran.');
    }
  }

  /// Best-effort cancel of a still-pending session. Safe to call on dispose
  /// even if the state isn't `pending` — we just no-op in that case.
  Future<void> cancelIfPending() async {
    final current = state;
    if (current is! PaymentPendingData) return;
    final id = current.paymentSessionId;
    try {
      await _api.post(
        '/api/client/payment-sessions/$id/cancel',
        data: const <String, dynamic>{},
      );
    } catch (_) {
      // Best-effort — backend sweeper will expire stale `pending` rows
      // after `payment_session_timeout_minutes` regardless.
    }
  }

  /// Reset to initial — used when the screen is re-entered for a new attempt.
  void reset() {
    state = const PaymentInitialData();
  }

  String _humanError(DioException e, {required String fallback}) {
    final code = e.response?.data?['error']?['code'] as String?;
    final status = e.response?.statusCode;
    if (status == 422 || code == 'VALIDATION_ERROR' || code == 'INVALID_TIER') {
      return 'Pilihan durasi tidak valid.';
    }
    if (status == 403) return 'Sesi tidak diizinkan.';
    if (status == 404) return 'Sesi pembayaran tidak ditemukan.';
    if (code == 'EXPIRED') return 'Sesi pembayaran sudah kedaluwarsa.';
    return fallback;
  }
}

/// Mirror of backend `PaymentSessionStatus` for any UI that needs to inspect
/// the raw status field (kept tiny for now — most flows route via state above).
class PaymentStatus {
  static const pending = PaymentSessionStatus.pending;
  static const confirmed = PaymentSessionStatus.confirmed;
  static const consumed = PaymentSessionStatus.consumed;
  PaymentStatus._();
}
