package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.api.dto.finance_workflow.response.TransactionApprovalResponse;
import dev.lions.unionflow.server.service.ApprovalService;
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.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.UUID;
import org.junit.jupiter.api.Test;

/**
 * Tests d'intégration REST pour ApprovalResource.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-20
 */
@QuarkusTest
class ApprovalResourceTest {

    private static final String BASE_PATH = "/api/finance/approvals";
    private static final String APPROVAL_ID = "00000000-0000-0000-0000-000000000003";
    private static final String ORG_ID = "00000000-0000-0000-0000-000000000001";

    @InjectMock
    ApprovalService approvalService;

    // -------------------------------------------------------------------------
    // POST /api/finance/approvals
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_missingFields_returns400() {
        // transactionType absent → vérification null → 400
        String body = """
                {"transactionId":"00000000-0000-0000-0000-000000000001","amount":500000}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_transactionIdNull_returns400() {
        // transactionId == null → 400 (couvre la branche transactionId == null dans if)
        String body = """
                {"transactionType":"CONTRIBUTION","amount":500000}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_amountNull_returns400() {
        // amount absent → rawAmount=null → amount=null → condition amount==null true → 400
        String body = """
                {"transactionId":"00000000-0000-0000-0000-000000000001","transactionType":"CONTRIBUTION"}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_illegalArgument_returns400() {
        when(approvalService.requestApproval(any(UUID.class), anyString(), anyDouble(), any()))
                .thenThrow(new IllegalArgumentException("transactionId invalide"));

        String body = """
                {"transactionId":"00000000-0000-0000-0000-000000000001",
                 "transactionType":"CONTRIBUTION","amount":500000}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_runtimeException_returns500() {
        when(approvalService.requestApproval(any(UUID.class), anyString(), anyDouble(), any()))
                .thenThrow(new RuntimeException("Erreur interne"));

        String body = """
                {"transactionId":"00000000-0000-0000-0000-000000000001",
                 "transactionType":"CONTRIBUTION","amount":500000}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_success_returns201() {
        TransactionApprovalResponse response = TransactionApprovalResponse.builder()
                .id(UUID.randomUUID())
                .status("PENDING")
                .build();
        when(approvalService.requestApproval(any(UUID.class), anyString(), anyDouble(), any()))
                .thenReturn(response);

        String body = """
                {"transactionId":"00000000-0000-0000-0000-000000000001",
                 "transactionType":"CONTRIBUTION","amount":500000}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(201)
                .contentType(ContentType.JSON);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void requestApproval_withOrganizationId_returns201() {
        TransactionApprovalResponse response = TransactionApprovalResponse.builder()
                .id(UUID.randomUUID())
                .status("PENDING")
                .build();
        when(approvalService.requestApproval(any(UUID.class), anyString(), anyDouble(), any(UUID.class)))
                .thenReturn(response);

        String body = """
                {"transactionId":"00000000-0000-0000-0000-000000000001",
                 "transactionType":"CONTRIBUTION","amount":500000,
                 "organizationId":"%s"}
                """.formatted(ORG_ID);

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(201)
                .contentType(ContentType.JSON);
    }

    // -------------------------------------------------------------------------
    // GET /api/finance/approvals/pending
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getPendingApprovals_returns200() {
        when(approvalService.getPendingApprovals(any()))
                .thenReturn(Collections.emptyList());

        given()
                .queryParam("organizationId", ORG_ID)
                .when()
                .get(BASE_PATH + "/pending")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getPendingApprovals_runtimeException_returns500() {
        when(approvalService.getPendingApprovals(any()))
                .thenThrow(new RuntimeException("DB error"));

        given()
                .when()
                .get(BASE_PATH + "/pending")
                .then()
                .statusCode(500);
    }

    // -------------------------------------------------------------------------
    // GET /api/finance/approvals/{approvalId}
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalById_notFound_returns404() {
        when(approvalService.getApprovalById(any(UUID.class)))
                .thenThrow(new NotFoundException("Approbation non trouvée"));

        given()
                .pathParam("approvalId", APPROVAL_ID)
                .when()
                .get(BASE_PATH + "/{approvalId}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalById_found_returns200() {
        TransactionApprovalResponse response = TransactionApprovalResponse.builder()
                .id(UUID.fromString(APPROVAL_ID))
                .status("PENDING")
                .build();
        when(approvalService.getApprovalById(any(UUID.class))).thenReturn(response);

        given()
                .pathParam("approvalId", APPROVAL_ID)
                .when()
                .get(BASE_PATH + "/{approvalId}")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalById_runtimeException_returns500() {
        when(approvalService.getApprovalById(any(UUID.class)))
                .thenThrow(new RuntimeException("Unexpected"));

        given()
                .pathParam("approvalId", APPROVAL_ID)
                .when()
                .get(BASE_PATH + "/{approvalId}")
                .then()
                .statusCode(500);
    }

    // -------------------------------------------------------------------------
    // POST /api/finance/approvals/{approvalId}/approve
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void approveTransaction_notFound_returns404() {
        when(approvalService.approveTransaction(any(UUID.class), any()))
                .thenThrow(new NotFoundException("Approbation non trouvée"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"comment\":\"OK\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/approve")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void approveTransaction_forbidden_returns403() {
        when(approvalService.approveTransaction(any(UUID.class), any()))
                .thenThrow(new ForbiddenException("Vous ne pouvez pas approuver votre propre demande"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"comment\":\"OK\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/approve")
                .then()
                .statusCode(403);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void approveTransaction_runtimeException_returns500() {
        when(approvalService.approveTransaction(any(UUID.class), any()))
                .thenThrow(new RuntimeException("DB error"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"comment\":\"OK\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/approve")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void approveTransaction_success_returns200() {
        TransactionApprovalResponse response = TransactionApprovalResponse.builder()
                .id(UUID.fromString(APPROVAL_ID))
                .status("APPROVED")
                .build();
        when(approvalService.approveTransaction(any(UUID.class), any())).thenReturn(response);

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"comment\":\"Approuvé\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/approve")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    // -------------------------------------------------------------------------
    // POST /api/finance/approvals/{approvalId}/reject
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void rejectTransaction_notFound_returns404() {
        when(approvalService.rejectTransaction(any(UUID.class), any()))
                .thenThrow(new NotFoundException("Approbation non trouvée"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"reason\":\"Motif refus\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/reject")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void rejectTransaction_forbidden_returns403() {
        when(approvalService.rejectTransaction(any(UUID.class), any()))
                .thenThrow(new ForbiddenException("Cette approbation ne peut plus être rejetée"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"reason\":\"Motif refus\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/reject")
                .then()
                .statusCode(403);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void rejectTransaction_runtimeException_returns500() {
        when(approvalService.rejectTransaction(any(UUID.class), any()))
                .thenThrow(new RuntimeException("DB error"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"reason\":\"Motif refus\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/reject")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void rejectTransaction_success_returns200() {
        TransactionApprovalResponse response = TransactionApprovalResponse.builder()
                .id(UUID.fromString(APPROVAL_ID))
                .status("REJECTED")
                .build();
        when(approvalService.rejectTransaction(any(UUID.class), any())).thenReturn(response);

        given()
                .contentType(ContentType.JSON)
                .pathParam("approvalId", APPROVAL_ID)
                .body("{\"reason\":\"Motif refus\"}")
                .when()
                .post(BASE_PATH + "/{approvalId}/reject")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    // -------------------------------------------------------------------------
    // GET /api/finance/approvals/history
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalsHistory_illegalArg_returns400() {
        when(approvalService.getApprovalsHistory(isNull(), any(), any(), any()))
                .thenThrow(new IllegalArgumentException("L'ID de l'organisation est requis"));

        given()
                .when()
                .get(BASE_PATH + "/history")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalsHistory_returns200() {
        when(approvalService.getApprovalsHistory(any(), any(), any(), any()))
                .thenReturn(Collections.emptyList());

        given()
                .queryParam("organizationId", ORG_ID)
                .when()
                .get(BASE_PATH + "/history")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalsHistory_withDates_returns200() {
        when(approvalService.getApprovalsHistory(any(), any(), any(), any()))
                .thenReturn(Collections.emptyList());

        given()
                .queryParam("organizationId", ORG_ID)
                .queryParam("startDate", "2026-01-01T00:00:00")
                .queryParam("endDate", "2026-03-31T23:59:59")
                .queryParam("status", "APPROVED")
                .when()
                .get(BASE_PATH + "/history")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getApprovalsHistory_runtimeException_returns500() {
        when(approvalService.getApprovalsHistory(any(), any(), any(), any()))
                .thenThrow(new RuntimeException("DB error"));

        given()
                .queryParam("organizationId", ORG_ID)
                .when()
                .get(BASE_PATH + "/history")
                .then()
                .statusCode(500);
    }

    // -------------------------------------------------------------------------
    // GET /api/finance/approvals/count/pending
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void countPendingApprovals_returns200() {
        when(approvalService.countPendingApprovals(any())).thenReturn(5L);

        given()
                .queryParam("organizationId", ORG_ID)
                .when()
                .get(BASE_PATH + "/count/pending")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("count", equalTo(5));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void countPendingApprovals_runtimeException_returns500() {
        when(approvalService.countPendingApprovals(any()))
                .thenThrow(new RuntimeException("DB error"));

        given()
                .when()
                .get(BASE_PATH + "/count/pending")
                .then()
                .statusCode(500);
    }
}
