package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.paiement.request.CreatePaiementRequest;
import dev.lions.unionflow.server.api.dto.paiement.request.DeclarerPaiementManuelRequest;
import dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementSummaryResponse;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.Paiement;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.PaiementRepository;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.*;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class PaiementServiceTest {

    @Inject
    PaiementService paiementService;

    @Inject
    MembreService membreService;

    @Inject
    MembreRepository membreRepository;

    @Inject
    OrganisationRepository organisationRepository;

    @Inject
    CotisationRepository cotisationRepository;

    @Inject
    PaiementRepository paiementRepository;

    private static final String TEST_USER_EMAIL = "membre-paiement-test@unionflow.dev";
    private Membre testMembre;
    private Organisation testOrganisation;
    private Cotisation testCotisation;

    @BeforeEach
    void setup() {
        // Créer Organisation
        testOrganisation = Organisation.builder()
                .nom("Org Paiement Test")
                .typeOrganisation("ASSOCIATION")
                .statut("ACTIVE")
                .email("org-pay-svc-" + System.currentTimeMillis() + "@test.com")
                .build();
        testOrganisation.setDateCreation(LocalDateTime.now());
        testOrganisation.setActif(true);
        organisationRepository.persist(testOrganisation);

        // Créer Membre (même email que TestSecurity ; rollback via @TestTransaction évite doublon)
        testMembre = new Membre();
        testMembre.setPrenom("Robert");
        testMembre.setNom("Payeur");
        testMembre.setEmail(TEST_USER_EMAIL);
        testMembre.setNumeroMembre("M-" + UUID.randomUUID().toString().substring(0, 8));
        testMembre.setDateNaissance(LocalDate.of(1975, 3, 15));
        testMembre.setStatutCompte("ACTIF");
        testMembre.setActif(true);
        testMembre.setDateCreation(LocalDateTime.now());
        membreRepository.persist(testMembre);

        // Créer Cotisation
        testCotisation = Cotisation.builder()
                .typeCotisation("MENSUELLE")
                .libelle("Cotisation test paiement")
                .montantDu(BigDecimal.valueOf(5000))
                .montantPaye(BigDecimal.ZERO)
                .codeDevise("XOF")
                .statut("EN_ATTENTE")
                .dateEcheance(LocalDate.now().plusMonths(1))
                .annee(LocalDate.now().getYear())
                .membre(testMembre)
                .organisation(testOrganisation)
                .build();
        testCotisation.setNumeroReference(Cotisation.genererNumeroReference());
        testCotisation.setDateCreation(LocalDateTime.now());
        testCotisation.setActif(true);
        cotisationRepository.persist(testCotisation);
    }

    @AfterEach
    @Transactional
    void tearDown() {
        // Supprimer Paiements du membre (Paiement n'a pas de lien direct cotisation, lien via PaiementObjet)
        if (testMembre != null && testMembre.getId() != null) {
            paiementRepository.getEntityManager()
                    .createQuery("DELETE FROM Paiement p WHERE p.membre.id = :membreId")
                    .setParameter("membreId", testMembre.getId())
                    .executeUpdate();
        }
        // Supprimer Cotisation
        if (testCotisation != null && testCotisation.getId() != null) {
            cotisationRepository.findByIdOptional(testCotisation.getId())
                    .ifPresent(cotisationRepository::delete);
        }
        // Supprimer Membre
        if (testMembre != null && testMembre.getId() != null) {
            membreRepository.findByIdOptional(testMembre.getId())
                    .ifPresent(membreRepository::delete);
        }
        // Supprimer Organisation
        if (testOrganisation != null && testOrganisation.getId() != null) {
            organisationRepository.findByIdOptional(testOrganisation.getId())
                    .ifPresent(organisationRepository::delete);
        }
    }

    @Test
    @Order(1)
    @TestTransaction
    @DisplayName("creerPaiement avec données valides crée le paiement")
    void creerPaiement_validRequest_createsPaiement() {
        String ref = "PAY-" + UUID.randomUUID().toString().substring(0, 8);
        CreatePaiementRequest request = CreatePaiementRequest.builder()
                .numeroReference(ref)
                .montant(new BigDecimal("250.00"))
                .codeDevise("XOF")
                .methodePaiement("ESPECES")
                .membreId(testMembre.getId())
                .build();

        PaiementResponse response = paiementService.creerPaiement(request);

        assertThat(response).isNotNull();
        assertThat(response.getNumeroReference()).isEqualTo(ref);
        assertThat(response.getStatutPaiement()).isEqualTo("EN_ATTENTE");
    }

    @Test
    @Order(2)
    @TestTransaction
    @DisplayName("validerPaiement change le statut en VALIDE")
    void validerPaiement_updatesStatus() {
        CreatePaiementRequest request = CreatePaiementRequest.builder()
                .numeroReference("REF-VAL-" + UUID.randomUUID().toString().substring(0, 5))
                .montant(BigDecimal.TEN)
                .codeDevise("EUR")
                .methodePaiement("VIREMENT")
                .membreId(testMembre.getId())
                .build();
        PaiementResponse created = paiementService.creerPaiement(request);

        PaiementResponse validated = paiementService.validerPaiement(created.getId());

        assertThat(validated.getStatutPaiement()).isEqualTo("VALIDE");
        assertThat(validated.getDateValidation()).isNotNull();
    }

    @Test
    @Order(3)
    @TestTransaction
    @DisplayName("annulerPaiement change le statut en ANNULE")
    void annulerPaiement_updatesStatus() {
        CreatePaiementRequest request = CreatePaiementRequest.builder()
                .numeroReference("REF-ANN-" + UUID.randomUUID().toString().substring(0, 5))
                .montant(BigDecimal.ONE)
                .codeDevise("USD")
                .methodePaiement("CARTE")
                .membreId(testMembre.getId())
                .build();
        PaiementResponse created = paiementService.creerPaiement(request);

        PaiementResponse cancelled = paiementService.annulerPaiement(created.getId());

        assertThat(cancelled.getStatutPaiement()).isEqualTo("ANNULE");
    }

    @Test
    @Order(4)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("getMonHistoriquePaiements → retourne paiements validés du membre connecté")
    @Transactional
    void getMonHistoriquePaiements_returnsOnlyMemberValidatedPaiements() {
        // Créer un paiement validé
        Paiement paiement = new Paiement();
        paiement.setNumeroReference("PAY-HIST-" + UUID.randomUUID().toString().substring(0, 8));
        paiement.setMontant(BigDecimal.valueOf(5000));
        paiement.setCodeDevise("XOF");
        paiement.setMethodePaiement("ESPECES");
        paiement.setStatutPaiement("VALIDE");
        paiement.setDatePaiement(LocalDateTime.now());
        paiement.setDateValidation(LocalDateTime.now());
        paiement.setMembre(testMembre);
        paiement.setDateCreation(LocalDateTime.now());
        paiement.setActif(true);
        paiementRepository.persist(paiement);

        List<PaiementSummaryResponse> results = paiementService.getMonHistoriquePaiements(5);

        assertThat(results).isNotNull();
        assertThat(results).isNotEmpty();
        assertThat(results).allMatch(p -> p.statutPaiement().equals("VALIDE"));
        assertThat(results.get(0).id()).isEqualTo(paiement.getId());
    }

    @Test
    @Order(5)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("getMonHistoriquePaiements → respecte la limite")
    @Transactional
    void getMonHistoriquePaiements_respectsLimit() {
        // Créer 3 paiements validés
        for (int i = 0; i < 3; i++) {
            Paiement paiement = new Paiement();
            paiement.setNumeroReference("PAY-LIMIT-" + i + "-" + System.currentTimeMillis());
            paiement.setMontant(BigDecimal.valueOf(1000));
            paiement.setCodeDevise("XOF");
            paiement.setMethodePaiement("ESPECES");
            paiement.setStatutPaiement("VALIDE");
            paiement.setDatePaiement(LocalDateTime.now().minusDays(i));
            paiement.setDateValidation(LocalDateTime.now().minusDays(i));
            paiement.setMembre(testMembre);
            paiement.setDateCreation(LocalDateTime.now());
            paiement.setActif(true);
            paiementRepository.persist(paiement);
        }

        List<PaiementSummaryResponse> results = paiementService.getMonHistoriquePaiements(2);

        assertThat(results).isNotNull();
        assertThat(results).hasSize(2);
    }

    @Test
    @Order(6)
    @TestTransaction
    @TestSecurity(user = "membre-inexistant@test.com", roles = {"MEMBRE"})
    @DisplayName("getMonHistoriquePaiements → membre non trouvé → NotFoundException")
    void getMonHistoriquePaiements_membreNonTrouve_throws() {
        assertThatThrownBy(() -> paiementService.getMonHistoriquePaiements(5))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Membre non trouvé");
    }

    @Test
    @Order(7)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("initierPaiementEnLigne → crée paiement avec statut EN_ATTENTE")
    void initierPaiementEnLigne_createsPaiement() {
        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                testCotisation.getId(), "WAVE", "771234567");

        PaiementGatewayResponse response = paiementService.initierPaiementEnLigne(request);

        assertThat(response).isNotNull();
        assertThat(response.getTransactionId()).isNotNull();
        assertThat(response.getRedirectUrl()).isNotNull();
        assertThat(response.getStatut()).isEqualTo("EN_ATTENTE");
        assertThat(response.getMethodePaiement()).isEqualTo("WAVE");
        assertThat(response.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(5000));
    }

    @Test
    @Order(8)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("initierPaiementEnLigne → cotisation inexistante → NotFoundException")
    void initierPaiementEnLigne_cotisationInexistante_throws() {
        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                UUID.randomUUID(), "WAVE", "771234567");

        assertThatThrownBy(() -> paiementService.initierPaiementEnLigne(request))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Cotisation non trouvée");
    }

    @Test
    @Order(9)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("initierPaiementEnLigne → cotisation n'appartient pas au membre → IllegalArgumentException")
    @Transactional
    void initierPaiementEnLigne_cotisationNonAutorisee_throws() {
        // Créer un autre membre (numeroMembre max 20 caractères en base)
        Membre autreMembre = Membre.builder()
                .numeroMembre("M-A-" + UUID.randomUUID().toString().substring(0, 6))
                .nom("Autre")
                .prenom("Membre")
                .email("autre-membre-" + System.currentTimeMillis() + "@test.com")
                .dateNaissance(LocalDate.of(1985, 5, 5))
                .build();
        autreMembre.setDateCreation(LocalDateTime.now());
        autreMembre.setActif(true);
        membreRepository.persist(autreMembre);

        // Créer une cotisation pour l'autre membre
        Cotisation autreCotisation = Cotisation.builder()
                .typeCotisation("MENSUELLE")
                .libelle("Cotisation autre membre")
                .montantDu(BigDecimal.valueOf(3000))
                .montantPaye(BigDecimal.ZERO)
                .codeDevise("XOF")
                .statut("EN_ATTENTE")
                .dateEcheance(LocalDate.now().plusMonths(1))
                .annee(LocalDate.now().getYear())
                .membre(autreMembre)
                .organisation(testOrganisation)
                .build();
        autreCotisation.setNumeroReference(Cotisation.genererNumeroReference());
        autreCotisation.setDateCreation(LocalDateTime.now());
        autreCotisation.setActif(true);
        cotisationRepository.persist(autreCotisation);

        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                autreCotisation.getId(), "WAVE", "771234567");

        assertThatThrownBy(() -> paiementService.initierPaiementEnLigne(request))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("n'appartient pas au membre connecté");

        // Cleanup
        cotisationRepository.delete(autreCotisation);
        membreRepository.delete(autreMembre);
    }

    @Test
    @Order(10)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("declarerPaiementManuel → crée paiement avec statut EN_ATTENTE_VALIDATION")
    void declarerPaiementManuel_createsPaiement() {
        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                testCotisation.getId(), "ESPECES", "REF-MANUEL-001", "Paiement effectué au trésorier");

        PaiementResponse response = paiementService.declarerPaiementManuel(request);

        assertThat(response).isNotNull();
        assertThat(response.getId()).isNotNull();
        assertThat(response.getStatutPaiement()).isEqualTo("EN_ATTENTE_VALIDATION");
        assertThat(response.getMethodePaiement()).isEqualTo("ESPECES");
        assertThat(response.getReferenceExterne()).isEqualTo("REF-MANUEL-001");
        assertThat(response.getCommentaire()).isEqualTo("Paiement effectué au trésorier");
    }

    @Test
    @Order(11)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("declarerPaiementManuel → cotisation inexistante → NotFoundException")
    void declarerPaiementManuel_cotisationInexistante_throws() {
        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                UUID.randomUUID(), "ESPECES", "REF-001", "Test");

        assertThatThrownBy(() -> paiementService.declarerPaiementManuel(request))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Cotisation non trouvée");
    }

    @Test
    @Order(12)
    @TestTransaction
    @TestSecurity(user = TEST_USER_EMAIL, roles = {"MEMBRE"})
    @DisplayName("declarerPaiementManuel → cotisation n'appartient pas au membre → IllegalArgumentException")
    @Transactional
    void declarerPaiementManuel_cotisationNonAutorisee_throws() {
        // Créer un autre membre (numeroMembre max 20 caractères en base)
        Membre autreMembre = Membre.builder()
                .numeroMembre("M-A2-" + UUID.randomUUID().toString().substring(0, 6))
                .nom("Autre")
                .prenom("Membre")
                .email("autre-membre2-" + System.currentTimeMillis() + "@test.com")
                .dateNaissance(LocalDate.of(1985, 5, 5))
                .build();
        autreMembre.setDateCreation(LocalDateTime.now());
        autreMembre.setActif(true);
        membreRepository.persist(autreMembre);

        // Créer une cotisation pour l'autre membre
        Cotisation autreCotisation = Cotisation.builder()
                .typeCotisation("MENSUELLE")
                .libelle("Cotisation autre membre")
                .montantDu(BigDecimal.valueOf(3000))
                .montantPaye(BigDecimal.ZERO)
                .codeDevise("XOF")
                .statut("EN_ATTENTE")
                .dateEcheance(LocalDate.now().plusMonths(1))
                .annee(LocalDate.now().getYear())
                .membre(autreMembre)
                .organisation(testOrganisation)
                .build();
        autreCotisation.setNumeroReference(Cotisation.genererNumeroReference());
        autreCotisation.setDateCreation(LocalDateTime.now());
        autreCotisation.setActif(true);
        cotisationRepository.persist(autreCotisation);

        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                autreCotisation.getId(), "ESPECES", "REF-001", "Test");

        assertThatThrownBy(() -> paiementService.declarerPaiementManuel(request))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("n'appartient pas au membre connecté");

        // Cleanup
        cotisationRepository.delete(autreCotisation);
        membreRepository.delete(autreMembre);
    }

    @Test
    @Order(13)
    @TestTransaction
    @TestSecurity(user = "membre-inexistant@test.com", roles = {"MEMBRE"})
    @DisplayName("initierPaiementEnLigne → membre non trouvé → NotFoundException")
    void initierPaiementEnLigne_membreNonTrouve_throws() {
        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                testCotisation.getId(), "WAVE", "771234567");

        assertThatThrownBy(() -> paiementService.initierPaiementEnLigne(request))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Membre non trouvé");
    }

    @Test
    @Order(14)
    @TestTransaction
    @TestSecurity(user = "membre-inexistant@test.com", roles = {"MEMBRE"})
    @DisplayName("declarerPaiementManuel → membre non trouvé → NotFoundException")
    void declarerPaiementManuel_membreNonTrouve_throws() {
        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                testCotisation.getId(), "ESPECES", "REF-001", "Test");

        assertThatThrownBy(() -> paiementService.declarerPaiementManuel(request))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Membre non trouvé");
    }
}
