package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.comptabilite.request.CreateCompteComptableRequest;
import dev.lions.unionflow.server.api.dto.comptabilite.request.CreateEcritureComptableRequest;
import dev.lions.unionflow.server.api.dto.comptabilite.request.CreateJournalComptableRequest;
import dev.lions.unionflow.server.api.dto.comptabilite.response.CompteComptableResponse;
import dev.lions.unionflow.server.api.dto.comptabilite.response.EcritureComptableResponse;
import dev.lions.unionflow.server.api.dto.comptabilite.response.JournalComptableResponse;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import dev.lions.unionflow.server.service.ComptabiliteService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class ComptabiliteResourceTest {

    @InjectMock
    ComptabiliteService comptabiliteService;

    // ============================================================
    // COMPTES COMPTABLES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/comptes retourne 200 avec liste")
    void listerComptes_returns200() {
        CompteComptableResponse compte = new CompteComptableResponse();
        compte.setId(UUID.randomUUID());
        compte.setNumeroCompte("512001");
        compte.setLibelle("Banque principale");
        compte.setTypeCompte(TypeCompteComptable.TRESORERIE);

        when(comptabiliteService.listerTousLesComptes()).thenReturn(List.of(compte));

        given()
                .when()
                .get("/api/comptabilite/comptes")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/comptes retourne 200 avec liste vide")
    void listerComptes_emptyList_returns200() {
        when(comptabiliteService.listerTousLesComptes()).thenReturn(List.of());

        given()
                .when()
                .get("/api/comptabilite/comptes")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/comptes retourne 400 quand service échoue")
    void listerComptes_serviceThrows_returns400() {
        when(comptabiliteService.listerTousLesComptes()).thenThrow(new RuntimeException("DB error"));

        given()
                .when()
                .get("/api/comptabilite/comptes")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/comptes/{id} retourne 200 quand trouvé")
    void trouverCompte_found_returns200() {
        UUID id = UUID.randomUUID();
        CompteComptableResponse compte = new CompteComptableResponse();
        compte.setId(id);
        compte.setNumeroCompte("401001");
        compte.setLibelle("Fournisseurs");

        when(comptabiliteService.trouverCompteParId(id)).thenReturn(compte);

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/comptes/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/comptes/{id} retourne 404 quand non trouvé")
    void trouverCompte_notFound_returns404() {
        UUID id = UUID.randomUUID();
        when(comptabiliteService.trouverCompteParId(id))
                .thenThrow(new NotFoundException("Compte non trouvé"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/comptes/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/comptes/{id} retourne 400 quand erreur inattendue")
    void trouverCompte_unexpectedError_returns400() {
        UUID id = UUID.randomUUID();
        when(comptabiliteService.trouverCompteParId(id))
                .thenThrow(new RuntimeException("Erreur inattendue"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/comptes/{id}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/comptes retourne 201 avec données valides")
    void creerCompte_validRequest_returns201() {
        CompteComptableResponse created = new CompteComptableResponse();
        created.setId(UUID.randomUUID());
        created.setNumeroCompte("512999");
        created.setLibelle("Nouveau compte");

        when(comptabiliteService.creerCompteComptable(any(CreateCompteComptableRequest.class)))
                .thenReturn(created);

        Map<String, Object> body = Map.of(
                "numeroCompte", "512999",
                "libelle", "Nouveau compte",
                "typeCompte", "TRESORERIE",
                "classeComptable", 5
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/comptes")
                .then()
                .statusCode(201)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/comptes retourne 400 si numéro dupliqué")
    void creerCompte_duplicateNumero_returns400() {
        when(comptabiliteService.creerCompteComptable(any(CreateCompteComptableRequest.class)))
                .thenThrow(new IllegalArgumentException("Un compte avec ce numéro existe déjà"));

        Map<String, Object> body = Map.of(
                "numeroCompte", "512001",
                "libelle", "Banque",
                "typeCompte", "TRESORERIE",
                "classeComptable", 5
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/comptes")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/comptes retourne 400 si erreur générale")
    void creerCompte_generalError_returns400() {
        when(comptabiliteService.creerCompteComptable(any(CreateCompteComptableRequest.class)))
                .thenThrow(new RuntimeException("Erreur DB"));

        Map<String, Object> body = Map.of(
                "numeroCompte", "512002",
                "libelle", "Compte test",
                "typeCompte", "ACTIF",
                "classeComptable", 5
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/comptes")
                .then()
                .statusCode(400);
    }

    // ============================================================
    // JOURNAUX COMPTABLES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/journaux retourne 200 avec liste")
    void listerJournaux_returns200() {
        JournalComptableResponse journal = new JournalComptableResponse();
        journal.setId(UUID.randomUUID());
        journal.setCode("BQ");
        journal.setLibelle("Journal Banque");
        journal.setTypeJournal(TypeJournalComptable.BANQUE);

        when(comptabiliteService.listerTousLesJournaux()).thenReturn(List.of(journal));

        given()
                .when()
                .get("/api/comptabilite/journaux")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/journaux retourne 400 quand service échoue")
    void listerJournaux_serviceThrows_returns400() {
        when(comptabiliteService.listerTousLesJournaux())
                .thenThrow(new RuntimeException("Erreur de liste"));

        given()
                .when()
                .get("/api/comptabilite/journaux")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/journaux/{id} retourne 200 quand trouvé")
    void trouverJournal_found_returns200() {
        UUID id = UUID.randomUUID();
        JournalComptableResponse journal = new JournalComptableResponse();
        journal.setId(id);
        journal.setCode("VT");
        journal.setLibelle("Journal Ventes");

        when(comptabiliteService.trouverJournalParId(id)).thenReturn(journal);

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/journaux/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/journaux/{id} retourne 404 quand non trouvé")
    void trouverJournal_notFound_returns404() {
        UUID id = UUID.randomUUID();
        when(comptabiliteService.trouverJournalParId(id))
                .thenThrow(new NotFoundException("Journal non trouvé"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/journaux/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/journaux/{id} retourne 400 quand erreur inattendue")
    void trouverJournal_unexpectedError_returns400() {
        UUID id = UUID.randomUUID();
        when(comptabiliteService.trouverJournalParId(id))
                .thenThrow(new RuntimeException("Erreur inattendue"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/journaux/{id}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/journaux retourne 201 avec données valides")
    void creerJournal_validRequest_returns201() {
        JournalComptableResponse created = new JournalComptableResponse();
        created.setId(UUID.randomUUID());
        created.setCode("CA");
        created.setLibelle("Journal Caisse");
        created.setTypeJournal(TypeJournalComptable.CAISSE);

        when(comptabiliteService.creerJournalComptable(any(CreateJournalComptableRequest.class)))
                .thenReturn(created);

        Map<String, Object> body = Map.of(
                "code", "CA",
                "libelle", "Journal Caisse",
                "typeJournal", "CAISSE",
                "dateDebut", LocalDate.now().toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/journaux")
                .then()
                .statusCode(201)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/journaux retourne 400 si code dupliqué")
    void creerJournal_duplicateCode_returns400() {
        when(comptabiliteService.creerJournalComptable(any(CreateJournalComptableRequest.class)))
                .thenThrow(new IllegalArgumentException("Un journal avec ce code existe déjà"));

        Map<String, Object> body = Map.of(
                "code", "BQ",
                "libelle", "Journal Banque",
                "typeJournal", "BANQUE"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/journaux")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/journaux retourne 400 si erreur générale")
    void creerJournal_generalError_returns400() {
        when(comptabiliteService.creerJournalComptable(any(CreateJournalComptableRequest.class)))
                .thenThrow(new RuntimeException("Erreur DB"));

        Map<String, Object> body = Map.of(
                "code", "OD",
                "libelle", "Opérations diverses",
                "typeJournal", "OD"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/journaux")
                .then()
                .statusCode(400);
    }

    // ============================================================
    // ÉCRITURES COMPTABLES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/{id} retourne 200 quand trouvé")
    void trouverEcriture_found_returns200() {
        UUID id = UUID.randomUUID();
        EcritureComptableResponse ecriture = new EcritureComptableResponse();
        ecriture.setId(id);
        ecriture.setNumeroPiece("EC-2025-001");
        ecriture.setLibelle("Règlement fournisseur");

        when(comptabiliteService.trouverEcritureParId(id)).thenReturn(ecriture);

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/ecritures/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/{id} retourne 404 quand non trouvé")
    void trouverEcriture_notFound_returns404() {
        UUID id = UUID.randomUUID();
        when(comptabiliteService.trouverEcritureParId(id))
                .thenThrow(new NotFoundException("Écriture non trouvée"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/ecritures/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/{id} retourne 400 quand erreur inattendue")
    void trouverEcriture_unexpectedError_returns400() {
        UUID id = UUID.randomUUID();
        when(comptabiliteService.trouverEcritureParId(id))
                .thenThrow(new RuntimeException("Erreur DB"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/comptabilite/ecritures/{id}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/journal/{journalId} retourne 200")
    void listerEcrituresParJournal_returns200() {
        UUID journalId = UUID.randomUUID();
        EcritureComptableResponse ecriture = new EcritureComptableResponse();
        ecriture.setId(UUID.randomUUID());
        ecriture.setNumeroPiece("EC-2025-002");

        when(comptabiliteService.listerEcrituresParJournal(journalId))
                .thenReturn(List.of(ecriture));

        given()
                .pathParam("journalId", journalId)
                .when()
                .get("/api/comptabilite/ecritures/journal/{journalId}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/journal/{journalId} retourne 400 si erreur")
    void listerEcrituresParJournal_serviceThrows_returns400() {
        UUID journalId = UUID.randomUUID();
        when(comptabiliteService.listerEcrituresParJournal(journalId))
                .thenThrow(new RuntimeException("Erreur DB"));

        given()
                .pathParam("journalId", journalId)
                .when()
                .get("/api/comptabilite/ecritures/journal/{journalId}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/organisation/{organisationId} retourne 200")
    void listerEcrituresParOrganisation_returns200() {
        UUID organisationId = UUID.randomUUID();
        EcritureComptableResponse ecriture = new EcritureComptableResponse();
        ecriture.setId(UUID.randomUUID());
        ecriture.setNumeroPiece("EC-2025-003");

        when(comptabiliteService.listerEcrituresParOrganisation(organisationId))
                .thenReturn(List.of(ecriture));

        given()
                .pathParam("organisationId", organisationId)
                .when()
                .get("/api/comptabilite/ecritures/organisation/{organisationId}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/comptabilite/ecritures/organisation/{organisationId} retourne 400 si erreur")
    void listerEcrituresParOrganisation_serviceThrows_returns400() {
        UUID organisationId = UUID.randomUUID();
        when(comptabiliteService.listerEcrituresParOrganisation(organisationId))
                .thenThrow(new RuntimeException("Erreur DB"));

        given()
                .pathParam("organisationId", organisationId)
                .when()
                .get("/api/comptabilite/ecritures/organisation/{organisationId}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/ecritures retourne 201 avec données valides")
    void creerEcriture_validRequest_returns201() {
        EcritureComptableResponse created = new EcritureComptableResponse();
        created.setId(UUID.randomUUID());
        created.setNumeroPiece("EC-2025-004");
        created.setLibelle("Écriture test");

        when(comptabiliteService.creerEcritureComptable(any(CreateEcritureComptableRequest.class)))
                .thenReturn(created);

        Map<String, Object> ligne = Map.of(
                "numeroLigne", 1,
                "montantDebit", 1000,
                "montantCredit", 0,
                "libelle", "Débit"
        );

        UUID journalId = UUID.randomUUID();
        Map<String, Object> body = Map.of(
                "numeroPiece", "EC-2025-004",
                "libelle", "Écriture test",
                "dateEcriture", "2025-01-15",
                "journalId", journalId.toString(),
                "lignes", List.of(ligne)
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/ecritures")
                .then()
                .statusCode(201)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/ecritures retourne 400 si écriture non équilibrée")
    void creerEcriture_desequilibre_returns400() {
        when(comptabiliteService.creerEcritureComptable(any(CreateEcritureComptableRequest.class)))
                .thenThrow(new IllegalArgumentException("L'écriture n'est pas équilibrée"));

        UUID journalIdErr = UUID.randomUUID();
        Map<String, Object> body = Map.of(
                "numeroPiece", "EC-2025-ERR",
                "libelle", "Écriture non équilibrée",
                "dateEcriture", "2025-01-15",
                "journalId", journalIdErr.toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/ecritures")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/comptabilite/ecritures retourne 400 si erreur générale")
    void creerEcriture_generalError_returns400() {
        when(comptabiliteService.creerEcritureComptable(any(CreateEcritureComptableRequest.class)))
                .thenThrow(new RuntimeException("Erreur de persistance"));

        UUID journalIdErr2 = UUID.randomUUID();
        Map<String, Object> body = Map.of(
                "numeroPiece", "EC-2025-ERR2",
                "libelle", "Écriture test",
                "dateEcriture", "2025-01-15",
                "journalId", journalIdErr2.toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/comptabilite/ecritures")
                .then()
                .statusCode(400);
    }
}
