/// Implémentation du repository des cotisations via l'API backend
library contribution_repository_impl;

import 'package:injectable/injectable.dart';
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
import 'package:unionflow_mobile_apps/core/utils/logger.dart';
import '../../domain/repositories/contribution_repository.dart';
import '../models/contribution_model.dart';

/// Implémentation du repository des cotisations - appels API réels vers /api/cotisations
@LazySingleton(as: IContributionRepository)
class ContributionRepositoryImpl implements IContributionRepository {
  final ApiClient _apiClient;
  static const String _baseUrl = '/api/cotisations';

  ContributionRepositoryImpl(this._apiClient);

  /// Toutes les cotisations du membre connecté (GET /api/cotisations/mes-cotisations).
  Future<ContributionPageResult> getMesCotisations({int page = 0, int size = 50}) async {
    final response = await _apiClient.get(
      '$_baseUrl/mes-cotisations',
      queryParameters: {'page': page, 'size': size},
    );
    if (response.statusCode != 200) {
      throw Exception(
        'Erreur lors de la récupération des cotisations: ${response.statusCode}',
      );
    }
    final data = response.data;
    final List<dynamic> list = data is List ? data as List<dynamic> : <dynamic>[];
    final contributions = list.map((e) => _summaryToModel(e as Map<String, dynamic>)).toList();
    return ContributionPageResult(
      contributions: contributions,
      total: contributions.length,
      page: page,
      size: size,
      totalPages: list.isEmpty ? 0 : 1,
    );
  }

  /// Récupère les cotisations en attente du membre connecté (endpoint dédié).
  Future<ContributionPageResult> getMesCotisationsEnAttente() async {
    final path = '$_baseUrl/mes-cotisations/en-attente';
    final response = await _apiClient.get(path);
    if (response.statusCode != 200) {
      throw Exception(
        'Erreur lors de la récupération des cotisations: ${response.statusCode}',
      );
    }
    final data = response.data;
    final List<dynamic> list = data is List ? data : (data is Map ? (data['data'] ?? data['content'] ?? []) as List<dynamic>? ?? [] : []);
    final contributions = list
        .map((e) => _summaryToModel(e as Map<String, dynamic>))
        .toList();
    return ContributionPageResult(
      contributions: contributions,
      total: contributions.length,
      page: 0,
      size: contributions.length,
      totalPages: contributions.isEmpty ? 0 : 1,
    );
  }

  static ContributionModel _summaryToModel(Map<String, dynamic> json) {
    final id = json['id']?.toString();
    final statutStr = json['statut'] as String? ?? 'EN_ATTENTE';
    final statut = _mapStatut(statutStr);
    final montantDu = (json['montantDu'] as num?)?.toDouble() ?? 0.0;
    final montantPaye = (json['montantPaye'] as num?)?.toDouble();
    final dateEcheanceStr = json['dateEcheance'] as String?;
    final dateEcheance = dateEcheanceStr != null
        ? DateTime.tryParse(dateEcheanceStr) ?? DateTime.now()
        : DateTime.now();
    final annee = (json['annee'] as num?)?.toInt() ?? dateEcheance.year;
    return ContributionModel(
      id: id,
      membreId: '', // membre implicite (endpoint "mes cotisations")
      membreNom: (json['nomMembre'] ?? json['nomCompletMembre']) as String?,
      type: ContributionType.annuelle,
      statut: statut,
      montant: montantDu,
      montantPaye: montantPaye,
      devise: 'XOF',
      dateEcheance: dateEcheance,
      annee: annee,
    );
  }

  static ContributionStatus _mapStatut(String code) {
    switch (code.toUpperCase()) {
      case 'PAYEE':
        return ContributionStatus.payee;
      case 'EN_RETARD':
        return ContributionStatus.enRetard;
      case 'PARTIELLE':
        return ContributionStatus.partielle;
      case 'ANNULEE':
        return ContributionStatus.annulee;
      case 'EN_ATTENTE':
      case 'NON_PAYEE':
      default:
        return ContributionStatus.nonPayee;
    }
  }

  /// Récupère la liste des cotisations avec pagination (toutes cotisations, nécessite droits admin)
  Future<ContributionPageResult> getCotisations({
    int page = 0,
    int size = 20,
    String? membreId,
    String? statut,
    String? type,
    int? annee,
  }) async {
    final queryParams = <String, dynamic>{
      'page': page,
      'size': size,
    };
    if (membreId != null) queryParams['membreId'] = membreId;
    if (statut != null) queryParams['statut'] = statut;
    if (type != null) queryParams['type'] = type;
    if (annee != null) queryParams['annee'] = annee;

    final response = await _apiClient.get(
      _baseUrl,
      queryParameters: queryParams,
    );

    if (response.statusCode == 200) {
      final data = response.data;
      if (data is List) {
        final contributions = data
            .map((json) => ContributionModel.fromJson(json as Map<String, dynamic>))
            .toList();
        return ContributionPageResult(
          contributions: contributions,
          total: contributions.length,
          page: page,
          size: size,
          totalPages: 1,
        );
      } else if (data is Map<String, dynamic>) {
        final List<dynamic> content = data['content'] ?? data['items'] ?? [];
        final contributions = content
            .map((json) => ContributionModel.fromJson(json as Map<String, dynamic>))
            .toList();
        return ContributionPageResult(
          contributions: contributions,
          total: data['totalElements'] ?? data['total'] ?? contributions.length,
          page: data['number'] ?? page,
          size: data['size'] ?? size,
          totalPages: data['totalPages'] ?? 1,
        );
      }
    }
    throw Exception('Erreur lors de la récupération des cotisations: ${response.statusCode}');
  }

  /// Récupère une cotisation par ID
  Future<ContributionModel> getCotisationById(String id) async {
    final response = await _apiClient.get('$_baseUrl/$id');
    if (response.statusCode == 200) {
      return ContributionModel.fromJson(response.data as Map<String, dynamic>);
    }
    throw Exception('Cotisation non trouvée');
  }

  /// Crée une nouvelle cotisation (payload conforme au backend CreateCotisationRequest)
  Future<ContributionModel> createCotisation(ContributionModel contribution) async {
    final body = _toCreateCotisationRequest(contribution);
    final response = await _apiClient.post(_baseUrl, data: body);
    if (response.statusCode == 201 || response.statusCode == 200) {
      final data = Map<String, dynamic>.from(response.data as Map<String, dynamic>);
      _normalizeCotisationResponse(data);
      return ContributionModel.fromJson(data);
    }
    final message = response.data is Map
        ? (response.data as Map)['error'] ?? response.data.toString()
        : response.data?.toString() ?? 'Erreur ${response.statusCode}';
    throw Exception('Erreur lors de la création: $message');
  }

  /// Construit le body attendu par POST /api/cotisations (CreateCotisationRequest)
  static Map<String, dynamic> _toCreateCotisationRequest(ContributionModel c) {
    if (c.organisationId == null || c.organisationId!.trim().isEmpty) {
      throw Exception('L\'organisation du membre est requise pour créer une cotisation.');
    }
    final typeStr = _contributionTypeToBackend(c.type);
    final dateStr = _formatLocalDate(c.dateEcheance);
    final desc = c.description?.trim();
    final libelle = desc != null && desc.isNotEmpty
        ? (desc.length > 100 ? desc.substring(0, 100) : desc)
        : 'Cotisation $typeStr ${c.annee}';
    final description = desc != null && desc.isNotEmpty
        ? (desc.length > 500 ? desc.substring(0, 500) : desc)
        : null;
    return {
      'membreId': c.membreId,
      'organisationId': c.organisationId!.trim(),
      'typeCotisation': typeStr,
      'libelle': libelle,
      if (description != null) 'description': description,
      'montantDu': c.montant,
      'codeDevise': c.devise.length == 3 ? c.devise : 'XOF',
      'dateEcheance': dateStr,
      'periode': '${_monthName(c.dateEcheance.month)} ${c.dateEcheance.year}',
      'annee': c.annee,
      'mois': c.mois ?? c.dateEcheance.month,
      'recurrente': false,
      if (c.notes != null && c.notes!.isNotEmpty) 'observations': c.notes,
    };
  }

  static String _contributionTypeToBackend(ContributionType t) {
    switch (t) {
      case ContributionType.mensuelle:
        return 'MENSUELLE';
      case ContributionType.trimestrielle:
        return 'TRIMESTRIELLE';
      case ContributionType.semestrielle:
        return 'SEMESTRIELLE';
      case ContributionType.annuelle:
        return 'ANNUELLE';
      case ContributionType.exceptionnelle:
        return 'EXCEPTIONNELLE';
    }
  }

  static String _formatLocalDate(DateTime d) =>
      '${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';

  static String _monthName(int month) {
    const names = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
    return month >= 1 && month <= 12 ? names[month - 1] : 'Mois $month';
  }

  /// Adapte les clés de la réponse backend (CotisationResponse) vers le modèle mobile
  static void _normalizeCotisationResponse(Map<String, dynamic> data) {
    if (data.containsKey('nomMembre') && !data.containsKey('membreNom')) data['membreNom'] = data['nomMembre'];
    if (data.containsKey('nomOrganisation') && !data.containsKey('organisationNom')) data['organisationNom'] = data['nomOrganisation'];
    if (data.containsKey('codeDevise') && !data.containsKey('devise')) data['devise'] = data['codeDevise'];
    if (data.containsKey('montantDu') && !data.containsKey('montant')) data['montant'] = data['montantDu'];
    if (data['id'] != null && data['id'] is! String) data['id'] = data['id'].toString();
    if (data['membreId'] != null && data['membreId'] is! String) data['membreId'] = data['membreId'].toString();
    if (data['organisationId'] != null && data['organisationId'] is! String) data['organisationId'] = data['organisationId'].toString();
  }

  /// Met à jour une cotisation
  Future<ContributionModel> updateCotisation(String id, ContributionModel contribution) async {
    final response = await _apiClient.put('$_baseUrl/$id', data: contribution.toJson());
    if (response.statusCode == 200) {
      return ContributionModel.fromJson(response.data as Map<String, dynamic>);
    }
    throw Exception('Erreur lors de la mise à jour: ${response.statusCode}');
  }

  /// Supprime une cotisation
  Future<void> deleteCotisation(String id) async {
    final response = await _apiClient.delete('$_baseUrl/$id');
    if (response.statusCode != 200 && response.statusCode != 204) {
      throw Exception('Erreur lors de la suppression: ${response.statusCode}');
    }
  }

  /// Initie un paiement en ligne (Wave Checkout API).
  /// Retourne l'URL à ouvrir (wave_launch_url) pour que le membre confirme dans l'app Wave.
  /// Spec: https://docs.wave.com/checkout
  Future<WavePaiementInitResult> initierPaiementEnLigne({
    required String cotisationId,
    required String methodePaiement,
    required String numeroTelephone,
  }) async {
    final response = await _apiClient.post(
      '/api/paiements/initier-paiement-en-ligne',
      data: {
        'cotisationId': cotisationId,
        'methodePaiement': methodePaiement,
        'numeroTelephone': numeroTelephone.replaceAll(RegExp(r'\D'), ''),
      },
    );
    if (response.statusCode != 201 && response.statusCode != 200) {
      final msg = response.data is Map
          ? (response.data['message'] ?? response.data['error'] ?? response.statusCode)
          : response.statusCode;
      throw Exception('Impossible d\'initier le paiement: $msg');
    }
    final data = response.data is Map<String, dynamic>
        ? response.data as Map<String, dynamic>
        : Map<String, dynamic>.from(response.data as Map);
    return WavePaiementInitResult(
      redirectUrl: data['redirectUrl'] as String? ?? data['waveLaunchUrl'] as String? ?? '',
      waveLaunchUrl: data['waveLaunchUrl'] as String? ?? data['redirectUrl'] as String? ?? '',
      waveCheckoutSessionId: data['waveCheckoutSessionId'] as String?,
      clientReference: data['clientReference'] as String?,
      message: data['message'] as String? ?? 'Ouvrez Wave pour confirmer le paiement.',
    );
  }

  /// Enregistre un paiement
  Future<ContributionModel> enregistrerPaiement(
    String cotisationId, {
    required double montant,
    required DateTime datePaiement,
    required String methodePaiement,
    String? numeroPaiement,
    String? referencePaiement,
  }) async {
    final response = await _apiClient.post(
      '$_baseUrl/$cotisationId/paiement',
      data: {
        'montant': montant,
        'datePaiement': datePaiement.toIso8601String(),
        'methodePaiement': methodePaiement,
        if (numeroPaiement != null) 'numeroPaiement': numeroPaiement,
        if (referencePaiement != null) 'referencePaiement': referencePaiement,
      },
    );
    if (response.statusCode == 200) {
      return ContributionModel.fromJson(response.data as Map<String, dynamic>);
    }
    throw Exception('Erreur lors de l\'enregistrement du paiement: ${response.statusCode}');
  }

  /// Synthèse personnelle du membre connecté (GET /api/cotisations/mes-cotisations/synthese)
  Future<Map<String, dynamic>?> getMesCotisationsSynthese() async {
    try {
      final response = await _apiClient.get('$_baseUrl/mes-cotisations/synthese');
      if (response.statusCode == 200 && response.data != null) {
        final data = response.data is Map<String, dynamic>
            ? response.data as Map<String, dynamic>
            : Map<String, dynamic>.from(response.data as Map);
        data['isMesSynthese'] = true;
        return data;
      }
      return null;
    } catch (e, st) {
      AppLogger.error('ContributionRepository: getMesCotisationsSynthese échoué', error: e, stackTrace: st);
      rethrow;
    }
  }

  /// Récupère les statistiques des cotisations (globales ou mes selon usage)
  Future<Map<String, dynamic>> getStatistiques() async {
    final response = await _apiClient.get('$_baseUrl/statistiques');
    if (response.statusCode == 200) {
      return response.data as Map<String, dynamic>;
    }
    throw Exception('Erreur lors de la récupération des statistiques');
  }

  /// Envoie un rappel de paiement
  Future<void> envoyerRappel(String cotisationId) async {
    final response = await _apiClient.post('$_baseUrl/$cotisationId/rappel');
    if (response.statusCode != 200) {
      throw Exception('Erreur lors de l\'envoi du rappel');
    }
  }

  /// Génère les cotisations annuelles
  Future<int> genererCotisationsAnnuelles(int annee) async {
    final response = await _apiClient.post(
      '$_baseUrl/generer',
      data: {'annee': annee},
    );
    if (response.statusCode == 200) {
      return response.data['nombreGenere'] ?? 0;
    }
    throw Exception('Erreur lors de la génération');
  }
}

/// Résultat de l'initiation d'un paiement Wave (redirection vers l'app Wave).
class WavePaiementInitResult {
  final String redirectUrl;
  final String waveLaunchUrl;
  final String? waveCheckoutSessionId;
  final String? clientReference;
  final String message;

  const WavePaiementInitResult({
    required this.redirectUrl,
    required this.waveLaunchUrl,
    this.waveCheckoutSessionId,
    this.clientReference,
    required this.message,
  });
}

/// Résultat paginé de cotisations
class ContributionPageResult {
  final List<ContributionModel> contributions;
  final int total;
  final int page;
  final int size;
  final int totalPages;

  const ContributionPageResult({
    required this.contributions,
    required this.total,
    required this.page,
    required this.size,
    required this.totalPages,
  });
}
