package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
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.BudgetResponse;
import dev.lions.unionflow.server.service.BudgetService;
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.BadRequestException;
import jakarta.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.Test;

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

    private static final String BASE_PATH = "/api/finance/budgets";
    private static final String ORG_ID = "00000000-0000-0000-0000-000000000001";
    private static final String BUDGET_ID = "00000000-0000-0000-0000-000000000002";

    @InjectMock
    BudgetService budgetService;

    // -------------------------------------------------------------------------
    // GET /api/finance/budgets
    // -------------------------------------------------------------------------

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

        given()
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(400);
    }

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

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

    // -------------------------------------------------------------------------
    // GET /api/finance/budgets/{budgetId}
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgetById_notFound_returns404() {
        when(budgetService.getBudgetById(any(UUID.class)))
                .thenThrow(new NotFoundException("Budget non trouvé"));

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

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgetById_found_returns200() {
        BudgetResponse response = BudgetResponse.builder()
                .id(UUID.fromString(BUDGET_ID))
                .name("Test Budget")
                .status("DRAFT")
                .build();
        when(budgetService.getBudgetById(any(UUID.class))).thenReturn(response);

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

    // -------------------------------------------------------------------------
    // POST /api/finance/budgets
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void createBudget_orgNotFound_returns404() {
        when(budgetService.createBudget(any()))
                .thenThrow(new NotFoundException("Organisation non trouvée"));

        String body = """
                {"name":"Test","organizationId":"00000000-0000-0000-0000-000000000001",
                 "period":"ANNUAL","year":2026,
                 "lines":[{"category":"CONTRIBUTIONS","name":"Cotisations","amountPlanned":100000}]}
                """;

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

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void createBudget_badRequest_returns400() {
        when(budgetService.createBudget(any()))
                .thenThrow(new BadRequestException("Le mois est requis pour un budget mensuel"));

        String body = """
                {"name":"Test","organizationId":"00000000-0000-0000-0000-000000000001",
                 "period":"ANNUAL","year":2026,
                 "lines":[{"category":"CONTRIBUTIONS","name":"Cotisations","amountPlanned":100000}]}
                """;

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

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void createBudget_success_returns201() {
        BudgetResponse response = BudgetResponse.builder()
                .id(UUID.randomUUID())
                .name("Test")
                .status("DRAFT")
                .build();
        when(budgetService.createBudget(any())).thenReturn(response);

        String body = """
                {"name":"Test","organizationId":"00000000-0000-0000-0000-000000000001",
                 "period":"ANNUAL","year":2026,
                 "lines":[{"category":"CONTRIBUTIONS","name":"Cotisations","amountPlanned":100000}]}
                """;

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

    // -------------------------------------------------------------------------
    // GET /api/finance/budgets/{budgetId}/tracking
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgetTracking_notFound_returns404() {
        when(budgetService.getBudgetTracking(any(UUID.class)))
                .thenThrow(new NotFoundException("Budget non trouvé"));

        given()
                .pathParam("budgetId", BUDGET_ID)
                .when()
                .get(BASE_PATH + "/{budgetId}/tracking")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgetTracking_found_returns200() {
        when(budgetService.getBudgetTracking(any(UUID.class)))
                .thenReturn(Map.of("budgetId", BUDGET_ID, "totalPlanned", 1000000));

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

    // -------------------------------------------------------------------------
    // PUT /api/finance/budgets/{budgetId}
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void updateBudget_notFound_returns404() {
        when(budgetService.updateBudget(any(UUID.class), any()))
                .thenThrow(new NotFoundException("Budget non trouvé"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("budgetId", BUDGET_ID)
                .body("{\"name\":\"Updated\"}")
                .when()
                .put(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void updateBudget_badRequest_returns400() {
        when(budgetService.updateBudget(any(UUID.class), any()))
                .thenThrow(new BadRequestException("Statut invalide"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("budgetId", BUDGET_ID)
                .body("{\"name\":\"Updated\"}")
                .when()
                .put(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void updateBudget_success_returns200() {
        BudgetResponse response = BudgetResponse.builder()
                .id(UUID.fromString(BUDGET_ID))
                .name("Updated")
                .status("DRAFT")
                .build();
        when(budgetService.updateBudget(any(UUID.class), any())).thenReturn(response);

        given()
                .contentType(ContentType.JSON)
                .pathParam("budgetId", BUDGET_ID)
                .body("{\"name\":\"Updated\"}")
                .when()
                .put(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    // -------------------------------------------------------------------------
    // Server error (500) paths — catch (Exception e) branches
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgets_serverError_returns500() {
        when(budgetService.getBudgets(any(), any(), any()))
                .thenThrow(new RuntimeException("unexpected db error"));

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

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgetById_serverError_returns500() {
        when(budgetService.getBudgetById(any(UUID.class)))
                .thenThrow(new RuntimeException("unexpected db error"));

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

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void createBudget_serverError_returns500() {
        when(budgetService.createBudget(any()))
                .thenThrow(new RuntimeException("unexpected db error"));

        String body = """
                {"name":"Test","organizationId":"00000000-0000-0000-0000-000000000001",
                 "period":"ANNUAL","year":2026,
                 "lines":[{"category":"CONTRIBUTIONS","name":"Cotisations","amountPlanned":100000}]}
                """;

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

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void getBudgetTracking_serverError_returns500() {
        when(budgetService.getBudgetTracking(any(UUID.class)))
                .thenThrow(new RuntimeException("unexpected db error"));

        given()
                .pathParam("budgetId", BUDGET_ID)
                .when()
                .get(BASE_PATH + "/{budgetId}/tracking")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void updateBudget_serverError_returns500() {
        when(budgetService.updateBudget(any(UUID.class), any()))
                .thenThrow(new RuntimeException("unexpected db error"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("budgetId", BUDGET_ID)
                .body("{\"name\":\"Updated\"}")
                .when()
                .put(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void deleteBudget_serverError_returns500() {
        doThrow(new RuntimeException("unexpected db error"))
                .when(budgetService).deleteBudget(any(UUID.class));

        given()
                .pathParam("budgetId", BUDGET_ID)
                .when()
                .delete(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(500);
    }

    // -------------------------------------------------------------------------
    // DELETE /api/finance/budgets/{budgetId}
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void deleteBudget_notFound_returns404() {
        doThrow(new NotFoundException("Budget non trouvé"))
                .when(budgetService).deleteBudget(any(UUID.class));

        given()
                .pathParam("budgetId", BUDGET_ID)
                .when()
                .delete(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION"})
    void deleteBudget_success_returns204() {
        doNothing().when(budgetService).deleteBudget(any(UUID.class));

        given()
                .pathParam("budgetId", BUDGET_ID)
                .when()
                .delete(BASE_PATH + "/{budgetId}")
                .then()
                .statusCode(204);
    }
}
