package dev.lions.unionflow.server.resource;

import dev.lions.unionflow.server.api.dto.membre.CompteAdherentResponse;
import dev.lions.unionflow.server.service.FirebasePushService;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.SouscriptionOrganisation;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.SouscriptionOrganisationRepository;
import dev.lions.unionflow.server.service.CompteAdherentService;
import dev.lions.unionflow.server.service.MembreKeycloakSyncService;
import dev.lions.unionflow.server.service.MembreService;
import dev.lions.unionflow.server.service.support.SecuriteHelper;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * Endpoint REST pour le compte adhérent du membre connecté.
 *
 * <p>Toutes les routes de ce resource sont protégées et ne retournent
 * que les données du membre <em>connecté</em> (pas d'accès aux comptes tiers).
 *
 * <p>Exemple de réponse :
 * <pre>
 * GET /api/membres/mon-compte
 * → { "numeroMembre": "MUF-2026-001", "soldeTotalDisponible": 215000, ... }
 * </pre>
 */
@Path("/api/membres")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Compte Adhérent", description = "Vue financière unifiée du membre connecté")
public class CompteAdherentResource {

    private static final Logger LOG = Logger.getLogger(CompteAdherentResource.class);

    @Inject
    CompteAdherentService compteAdherentService;

    @Inject
    SecuriteHelper securiteHelper;

    @Inject
    MembreRepository membreRepository;

    @Inject
    MembreOrganisationRepository membreOrganisationRepo;

    @Inject
    SouscriptionOrganisationRepository souscriptionRepo;

    @Inject
    MembreKeycloakSyncService membreKeycloakSyncService;

    @Inject
    MembreService membreService;

    @Inject
    FirebasePushService firebasePushService;

    /**
     * Enregistre ou met à jour le token FCM du membre connecté pour les notifications push.
     * Appelé par l'application mobile au démarrage ou quand Firebase renouvelle le token.
     */
    @PUT
    @Path("/mon-compte/fcm-token")
    @Authenticated
    @Operation(summary = "Enregistrer le token FCM pour les notifications push")
    @jakarta.transaction.Transactional
    public Response enregistrerFcmToken(Map<String, String> body) {
        String email = securiteHelper.resolveEmail();
        if (email == null) return Response.status(Response.Status.UNAUTHORIZED).build();

        String token = body != null ? body.get("token") : null;
        if (token == null || token.isBlank()) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(Map.of("message", "Le champ 'token' est requis.")).build();
        }

        return membreRepository.findByEmail(email)
                .map(membre -> {
                    membre.setFcmToken(token.trim());
                    membreRepository.persist(membre);
                    return Response.ok(Map.of("message", "Token FCM enregistré.")).build();
                })
                .orElse(Response.status(Response.Status.NOT_FOUND)
                        .entity(Map.of("message", "Membre introuvable.")).build());
    }

    /**
     * Supprime le token FCM (désabonnement des notifications push).
     */
    @DELETE
    @Path("/mon-compte/fcm-token")
    @Authenticated
    @Operation(summary = "Désactiver les notifications push")
    @jakarta.transaction.Transactional
    public Response supprimerFcmToken() {
        String email = securiteHelper.resolveEmail();
        if (email == null) return Response.status(Response.Status.UNAUTHORIZED).build();

        return membreRepository.findByEmail(email)
                .map(membre -> {
                    membre.setFcmToken(null);
                    membreRepository.persist(membre);
                    return Response.ok(Map.of("message", "Notifications push désactivées.")).build();
                })
                .orElse(Response.status(Response.Status.NOT_FOUND).build());
    }

    /**
     * Retourne le compte adhérent complet du membre connecté :
     * numéro de membre, soldes (cotisations + épargne), capacité d'emprunt, taux d'engagement.
     */
    @GET
    @Path("/mon-compte")
    @RolesAllowed({ "USER", "MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN" })
    @Operation(
        summary     = "Compte adhérent du membre connecté",
        description = "Agrège cotisations, épargne et crédit en une vue financière unifiée."
    )
    public Response getMonCompte() {
        CompteAdherentResponse compte = compteAdherentService.getMonCompte();
        return Response.ok(compte).build();
    }

    /**
     * Retourne le statut du compte du membre connecté.
     *
     * <p>Endpoint léger appelé par le mobile juste après le login Keycloak
     * pour détecter les comptes en attente/suspendus/désactivés avant d'accorder l'accès.
     *
     * <p>Si aucun enregistrement membre n'existe (ex. : administrateur créé directement
     * dans Keycloak sans fiche membre), retourne {@code ACTIF} pour ne pas bloquer les admins.
     */
    @GET
    @Path("/mon-statut")
    @Authenticated
    @Operation(
        summary     = "Statut du compte du membre connecté",
        description = "Retourne statutCompte : ACTIF, EN_ATTENTE_VALIDATION, SUSPENDU ou DESACTIVE."
    )
    public Response getMonStatut() {
        String email = securiteHelper.resolveEmail();
        if (email == null || email.isBlank()) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        Optional<Membre> membreOpt = membreRepository.findByEmail(email.trim())
                .or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));

        // Pas de fiche membre → administrateur pur → accès autorisé
        String statutCompte = membreOpt
                .map(Membre::getStatutCompte)
                .filter(s -> s != null && !s.isBlank())
                .orElse("ACTIF");

        // Premier login : Keycloak a déjà forcé UPDATE_PASSWORD dans le Chrome Custom Tab.
        // Si l'utilisateur possède un token valide (@Authenticated), c'est la preuve que
        // le changement de mot de passe est complété — mettre à jour la DB et assigner les rôles.
        // Pour les anciens comptes (sans UPDATE_PASSWORD assigné), le service assigne automatiquement
        // la required action et retourne REAUTH_REQUIS pour que Flutter déclenche une nouvelle auth.
        boolean premierLoginComplet = false;
        boolean reAuthRequired = false;
        if (membreOpt.isPresent() && Boolean.TRUE.equals(membreOpt.get().getPremiereConnexion())) {
            Membre m = membreOpt.get();
            MembreKeycloakSyncService.PremierLoginResultat resultat =
                    membreKeycloakSyncService.completerPremierLogin(m.getId());
            if (resultat == MembreKeycloakSyncService.PremierLoginResultat.COMPLETE) {
                premierLoginComplet = true;
                // Relire le statutCompte après activation éventuelle
                statutCompte = membreRepository.findByIdOptional(m.getId())
                        .map(Membre::getStatutCompte)
                        .orElse(statutCompte);
                LOG.infof("Premier login complété au statut pour %s → %s", m.getEmail(), statutCompte);
            } else if (resultat == MembreKeycloakSyncService.PremierLoginResultat.REAUTH_REQUIS) {
                reAuthRequired = true;
                LOG.infof("Réauthentification requise pour %s (UPDATE_PASSWORD assigné)", m.getEmail());
            }
        }

        // Fallback : auto-activer si EN_ATTENTE_VALIDATION et org a reçu un paiement.
        // Couvre le cas PAIEMENT_CONFIRME (admin a payé mais super admin n'a pas encore validé)
        // et ACTIVE/VALIDEE (chemin nominal). L'admin ne doit pas bloquer sur l'AwaitingValidationPage
        // dès lors que le paiement est confirmé côté Wave.
        if ("EN_ATTENTE_VALIDATION".equals(statutCompte) && membreOpt.isPresent()) {
            Membre m = membreOpt.get();
            UUID orgId = membreOrganisationRepo.findFirstByMembreId(m.getId())
                    .map(mo -> mo.getOrganisation().getId())
                    .orElse(null);
            if (membreService.orgHasPaidSubscription(orgId)) {
                LOG.infof("Auto-activation au login de %s (org %s a souscription payée)", m.getEmail(), orgId);
                membreService.activerMembre(m.getId());
                try {
                    membreKeycloakSyncService.activerMembreDansKeycloak(m.getId());
                } catch (Exception e) {
                    LOG.warnf("Activation Keycloak au login échouée pour %s (non bloquant): %s",
                            m.getEmail(), e.getMessage());
                }
                statutCompte = "ACTIF";
            }
        }

        Map<String, Object> response = new HashMap<>();
        response.put("statutCompte", statutCompte);
        // changerMotDePasseRequis = false : Keycloak gère nativement le changement de mot de passe
        // via la required action UPDATE_PASSWORD dans le Chrome Custom Tab (AppAuth).
        response.put("changerMotDePasseRequis", false);
        // Indique à Flutter que le token doit être rafraîchi (nouveaux rôles MEMBRE/MEMBRE_ACTIF)
        response.put("premierLoginComplet", premierLoginComplet);
        // Indique à Flutter qu'une réauthentification est nécessaire (UPDATE_PASSWORD vient d'être assigné)
        response.put("reAuthRequired", reAuthRequired);

        // Enrichir avec l'état d'onboarding pour les comptes en attente
        if ("EN_ATTENTE_VALIDATION".equals(statutCompte)) {
            membreOpt.flatMap(m -> membreOrganisationRepo.findFirstByMembreId(m.getId())
                    .map(MembreOrganisation::getOrganisation))
                    .ifPresent(org -> {
                response.put("organisationId", org.getId().toString());
                Optional<SouscriptionOrganisation> souscOpt =
                        souscriptionRepo.findLatestByOrganisationId(org.getId());

                if (souscOpt.isEmpty()) {
                    response.put("onboardingState", "NO_SUBSCRIPTION");
                } else {
                    SouscriptionOrganisation sosc = souscOpt.get();
                    String valState = sosc.getStatutValidation() != null
                            ? sosc.getStatutValidation().name()
                            : "EN_ATTENTE_PAIEMENT";
                    String onboardingState = switch (valState) {
                        case "EN_ATTENTE_PAIEMENT" -> "AWAITING_PAYMENT";
                        case "PAIEMENT_INITIE"     -> "PAYMENT_INITIATED";
                        case "PAIEMENT_CONFIRME"   -> "AWAITING_VALIDATION";
                        case "VALIDEE"             -> "VALIDATED";
                        case "REJETEE"             -> "REJECTED";
                        default                    -> "AWAITING_PAYMENT";
                    };
                    response.put("onboardingState", onboardingState);
                    response.put("souscriptionId", sosc.getId().toString());
                    if (sosc.getWaveSessionId() != null) {
                        response.put("waveSessionId", sosc.getWaveSessionId());
                    }
                }
                response.put("typeOrganisation", org.getTypeOrganisation() != null
                        ? org.getTypeOrganisation() : "ASSOCIATION");
            });
            if (!response.containsKey("onboardingState")) {
                response.put("onboardingState", "NO_SUBSCRIPTION");
            }
        }

        return Response.ok(response).build();
    }

    /**
     * Permet au membre connecté de changer son mot de passe lors du premier login.
     * Appelle Keycloak via lions-user-manager et marque {@code premiereConnexion = false}.
     *
     * <p>Body attendu : {@code { "nouveauMotDePasse": "..." }}
     */
    @PUT
    @Path("/mon-compte/mot-de-passe")
    @Authenticated
    @Operation(
        summary     = "Changer le mot de passe au premier login",
        description = "Met à jour le mot de passe Keycloak et lève le flag premiereConnexion."
    )
    public Response changerMotDePasse(Map<String, String> body) {
        String email = securiteHelper.resolveEmail();
        if (email == null || email.isBlank()) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        String nouveauMotDePasse = body == null ? null : body.get("nouveauMotDePasse");
        if (nouveauMotDePasse == null || nouveauMotDePasse.isBlank()) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(Map.of("message", "Le champ 'nouveauMotDePasse' est requis."))
                    .build();
        }

        Optional<Membre> membreOpt = membreRepository.findByEmail(email.trim())
                .or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));

        if (membreOpt.isEmpty()) {
            return Response.status(Response.Status.NOT_FOUND)
                    .entity(Map.of("message", "Aucun membre trouvé pour ce compte."))
                    .build();
        }

        membreKeycloakSyncService.changerMotDePassePremierLogin(membreOpt.get().getId(), nouveauMotDePasse);
        return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
    }

    /**
     * Endpoint mobile : changement de mot de passe depuis l'app Flutter.
     * Bypass lions-user-manager — appel direct à l'API Admin Keycloak.
     *
     * <p>Body attendu : {@code { "userId": "...", "oldPassword": "...", "newPassword": "..." }}
     */
    @POST
    @Path("/auth/change-password")
    @Authenticated
    @Operation(
        summary     = "Changer le mot de passe (mobile)",
        description = "Endpoint dédié à l'application mobile. Bypass lions-user-manager via API Admin Keycloak directe."
    )
    public Response changerMotDePasseMobile(Map<String, String> body) {
        String email = securiteHelper.resolveEmail();
        if (email == null || email.isBlank()) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        String newPassword = body == null ? null : body.get("newPassword");
        if (newPassword == null || newPassword.isBlank()) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(Map.of("message", "Le champ 'newPassword' est requis."))
                    .build();
        }
        if (newPassword.length() < 8) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(Map.of("message", "Le mot de passe doit contenir au moins 8 caractères."))
                    .build();
        }

        Optional<Membre> membreOpt = membreRepository.findByEmail(email.trim())
                .or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));

        if (membreOpt.isEmpty()) {
            return Response.status(Response.Status.NOT_FOUND)
                    .entity(Map.of("message", "Aucun membre trouvé pour ce compte."))
                    .build();
        }

        try {
            membreKeycloakSyncService.changerMotDePasseDirectKeycloak(membreOpt.get().getId(), newPassword);
            return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur changement mot de passe pour %s", email);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(Map.of("message", "Erreur lors du changement de mot de passe: " + e.getMessage()))
                    .build();
        }
    }
}
