WinController.java

package com.archiweb.api;

import com.archiweb.model.User;
import com.archiweb.model.Win;
import com.archiweb.repository.UserRepository;
import com.archiweb.repository.WinRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/api/wins")
@Tag(
        name = "Gains",
        description = """
        Module de gestion des **gains et lots remportés** par les participants.  
        Ce contrôleur permet :
        - La **création** manuelle de gains pour les tests ou corrections administratives.  
        - La **consultation paginée** de tous les gains enregistrés.  
        - La **consultation individuelle** des gains d’un utilisateur.  
        - La **validation** de la remise d’un lot (marquage comme “remis”).  
        Accessible uniquement aux administrateurs et employés autorisés.
        """
)
public class WinController {

    private final WinRepository winRepository;
    private final UserRepository userRepository;

    public WinController(WinRepository winRepository, UserRepository userRepository) {
        this.winRepository = winRepository;
        this.userRepository = userRepository;
    }

    // =========================================================
    // 1️⃣ Liste paginée des gains
    // =========================================================
    @Operation(
            summary = "Lister les gains (paginé)",
            description = """
            Renvoie une **liste paginée** de tous les gains enregistrés dans le système.  
            Ce point d’accès est principalement destiné aux administrateurs pour la supervision et le reporting.  
            Paramètres :
            - `page` : numéro de page (défaut : 0)  
            - `size` : taille de page (défaut : 10)
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Liste paginée des gains récupérée avec succès",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(
                                            value = """
                                            {
                                              "content": [
                                                {
                                                  "id": 1,
                                                  "user": { "id": 5, "email": "client@example.com" },
                                                  "prizeType": "COFFRET_39",
                                                  "claimed": false,
                                                  "createdAt": "2025-10-20T15:21:45"
                                                }
                                              ],
                                              "pageable": { "pageNumber": 0, "pageSize": 10 },
                                              "totalElements": 124
                                            }
                                            """
                                    )
                            )
                    )
            }
    )
    @GetMapping
    public ResponseEntity<Page<Win>> getAllWins(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size
    ) {
        Pageable pageable = PageRequest.of(page, size);
        Page<Win> wins = winRepository.findAll(pageable);
        return ResponseEntity.ok(wins);
    }

    // =========================================================
    // 2️⃣ Création manuelle d’un gain
    // =========================================================
    @Operation(
            summary = "Créer un gain manuellement",
            description = """
            Permet à un administrateur de **créer un gain pour un utilisateur existant**.  
            Cette opération est utile pour :
            - Simuler des cas de test,  
            - Ajouter un gain oublié,  
            - Corriger une erreur de saisie.  
            Le champ `user.id` doit être renseigné.
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Gain créé avec succès",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(
                                            value = """
                                            {
                                              "id": 58,
                                              "user": { "id": 3, "email": "client@example.com" },
                                              "prizeType": "INFUSEUR",
                                              "claimed": false,
                                              "createdAt": "2025-10-27T13:50:10"
                                            }
                                            """
                                    )
                            )
                    ),
                    @ApiResponse(
                            responseCode = "400",
                            description = "Utilisateur non trouvé ou non spécifié",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(value = "Utilisateur introuvable ou non spécifié")
                            )
                    )
            }
    )
    @PostMapping
    public ResponseEntity<?> createWin(@RequestBody Win newWin) {
        if (newWin.getUser() == null || newWin.getUser().getId() == null) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Utilisateur non spécifié pour le gain");
            error.put("message", "Utilisateur non spécifié pour le gain");
            return ResponseEntity.badRequest().body(error);
        }

        Optional<User> user = userRepository.findById(newWin.getUser().getId());
        if (user.isEmpty()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Utilisateur introuvable");
            error.put("message", "Utilisateur introuvable");
            return ResponseEntity.badRequest().body(error);
        }

        newWin.setUser(user.get());
        newWin.setClaimed(false);
        newWin.setCreatedAt(LocalDateTime.now());

        Win saved = winRepository.save(newWin);
        return ResponseEntity.ok(saved);
    }

    // =========================================================
    // 3️⃣ Gains d’un utilisateur spécifique
    // =========================================================
    @Operation(
            summary = "Lister les gains d’un utilisateur",
            description = """
            Renvoie la liste complète des gains associés à un utilisateur donné.  
            Ce point d’accès est utilisé par les administrateurs et les employés de boutique pour la vérification des lots.
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Liste des gains récupérée avec succès",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(
                                            value = """
                                            [
                                              {
                                                "id": 5,
                                                "prizeType": "COFFRET_99",
                                                "claimed": false,
                                                "createdAt": "2025-10-10T12:22:11"
                                              },
                                              {
                                                "id": 9,
                                                "prizeType": "INFUSEUR",
                                                "claimed": true,
                                                "claimedAt": "2025-10-14T10:03:52"
                                              }
                                            ]
                                            """
                                    )
                            )
                    ),
                    @ApiResponse(
                            responseCode = "400",
                            description = "Utilisateur introuvable",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(value = "Utilisateur introuvable")
                            )
                    )
            }
    )
    @GetMapping("/user/{id}")
    public ResponseEntity<?> getWinsByUser(@PathVariable Long id) {
        if (!userRepository.existsById(id)) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Utilisateur introuvable");
            error.put("message", "Utilisateur introuvable");
            return ResponseEntity.badRequest().body(error);
        }
        List<Win> wins = winRepository.findByUserId(id);
        return ResponseEntity.ok(wins);
    }

    // =========================================================
    // 4️⃣ Marquer un gain comme remis
    // =========================================================
    @Operation(
            summary = "Marquer un gain comme remis",
            description = """
            Met à jour un gain pour indiquer qu’il a été **remis au gagnant**.  
            L’opération est effectuée par un employé ou un administrateur et enregistre la date de remise (`claimedAt`).
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Gain marqué comme remis avec succès",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(
                                            value = """
                                            {
                                              "status": "success",
                                              "message": "Le gain a été marqué comme remis.",
                                              "claimedAt": "2025-10-27T14:12:09"
                                            }
                                            """
                                    )
                            )
                    ),
                    @ApiResponse(
                            responseCode = "400",
                            description = "Gain introuvable ou déjà remis",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(
                                            value = "Gain introuvable ou déjà remis"
                                    )
                            )
                    )
            }
    )
    @PatchMapping("/{id}/claim")
    public ResponseEntity<?> claimWin(@PathVariable Long id, Authentication authentication) {
        Optional<Win> optionalWin = winRepository.findById(id);
        if (optionalWin.isEmpty()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Gain introuvable");
            error.put("message", "Gain introuvable");
            return ResponseEntity.badRequest().body(error);
        }

        Win win = optionalWin.get();
        if (win.isClaimed()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Ce gain a déjà été remis");
            error.put("message", "Ce gain a déjà été remis");
            return ResponseEntity.badRequest().body(error);
        }

        win.setClaimed(true);
        win.setClaimedAt(LocalDateTime.now());
        winRepository.save(win);

        return ResponseEntity.ok("Le gain a été marqué comme remis");
    }

    // =========================================================
    // 5️⃣ Mettre à jour le choix de livraison d'un gain
    // =========================================================
    @Operation(
            summary = "Mettre à jour le choix de livraison d'un gain",
            description = """
            Met à jour un gain avec le choix de livraison (domicile ou boutique) et l'adresse si nécessaire.
            Cette opération est appelée après que l'utilisateur ait choisi son mode de réception.
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Choix de livraison mis à jour avec succès"
                    ),
                    @ApiResponse(
                            responseCode = "400",
                            description = "Gain introuvable"
                    )
            }
    )
    @PatchMapping("/{id}/delivery")
    public ResponseEntity<?> updateDeliveryChoice(
            @PathVariable Long id,
            @RequestBody Map<String, Object> deliveryData,
            Authentication authentication
    ) {
        Optional<Win> optionalWin = winRepository.findById(id);
        if (optionalWin.isEmpty()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Gain introuvable");
            error.put("message", "Gain introuvable");
            return ResponseEntity.badRequest().body(error);
        }

        Win win = optionalWin.get();
        
        // Vérifier que l'utilisateur peut modifier son propre gain
        if (authentication != null && win.getUser() != null) {
            String userEmail = authentication.getName();
            if (!win.getUser().getEmail().equals(userEmail)) {
                Map<String, String> error = new HashMap<>();
                error.put("error", "Vous ne pouvez modifier que vos propres gains");
                error.put("message", "Vous ne pouvez modifier que vos propres gains");
                return ResponseEntity.status(403).body(error);
            }
        }
        
        // Mettre à jour le type de livraison
        if (deliveryData.containsKey("deliveryType")) {
            win.setDeliveryType((String) deliveryData.get("deliveryType"));
        }
        
        // Mettre à jour l'adresse de livraison (en JSON)
        if (deliveryData.containsKey("address")) {
            Object addressObj = deliveryData.get("address");
            if (addressObj != null) {
                // Convertir l'objet en JSON string
                try {
                    com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
                    win.setDeliveryAddress(mapper.writeValueAsString(addressObj));
                } catch (Exception e) {
                    // Si la conversion échoue, stocker comme string
                    win.setDeliveryAddress(addressObj.toString());
                }
            }
        }

        winRepository.save(win);

        Map<String, Object> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "Choix de livraison mis à jour avec succès");
        return ResponseEntity.ok(response);
    }

    // =========================================================
    // 6️⃣ Récupérer les gains en livraison à domicile (non reçus)
    // =========================================================
    @Operation(
            summary = "Lister les gains en livraison à domicile non reçus",
            description = """
            Renvoie la liste des gains qui ont été choisis pour la livraison à domicile
            et qui n'ont pas encore été marqués comme reçus.
            Utilisé par les administrateurs pour suivre les livraisons en cours.
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Liste des gains en livraison récupérée avec succès"
                    )
            }
    )
    // Endpoint de test pour vérifier les permissions
    @GetMapping("/test-auth")
    public ResponseEntity<Map<String, Object>> testAuth(Authentication authentication) {
        Map<String, Object> response = new HashMap<>();
        response.put("authenticated", authentication != null);
        response.put("name", authentication != null ? authentication.getName() : null);
        response.put("authorities", authentication != null ? 
            authentication.getAuthorities().stream()
                .map(a -> a.getAuthority())
                .collect(java.util.stream.Collectors.toList()) : null);
        response.put("hasAdminRole", authentication != null && 
            authentication.getAuthorities().stream()
                .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")));
        response.put("hasEmployeeRole", authentication != null && 
            authentication.getAuthorities().stream()
                .anyMatch(a -> a.getAuthority().equals("ROLE_EMPLOYEE")));
        return ResponseEntity.ok(response);
    }

    @GetMapping("/home-delivery/pending")
    public ResponseEntity<List<Win>> getPendingHomeDeliveryWins() {
        List<Win> wins = winRepository.findHomeDeliveryWinsNotReceived();
        return ResponseEntity.ok(wins);
    }

    // =========================================================
    // 7️⃣ Marquer un gain comme reçu (pour livraison à domicile)
    // =========================================================
    @Operation(
            summary = "Marquer un gain comme reçu",
            description = """
            Met à jour un gain en livraison à domicile pour indiquer qu'il a été **reçu par le client**.
            L'opération est effectuée par un administrateur et enregistre la date de réception (`receivedAt`).
            """,
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Gain marqué comme reçu avec succès"
                    ),
                    @ApiResponse(
                            responseCode = "400",
                            description = "Gain introuvable ou déjà reçu"
                    )
            }
    )
    @PatchMapping("/{id}/receive")
    public ResponseEntity<?> markAsReceived(@PathVariable Long id, Authentication authentication) {
        Optional<Win> optionalWin = winRepository.findById(id);
        if (optionalWin.isEmpty()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Gain introuvable");
            error.put("message", "Gain introuvable");
            return ResponseEntity.badRequest().body(error);
        }

        Win win = optionalWin.get();
        
        // Vérifier que c'est bien un gain en livraison à domicile
        if (!"home".equals(win.getDeliveryType())) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Ce gain n'est pas en livraison à domicile");
            error.put("message", "Ce gain n'est pas en livraison à domicile");
            return ResponseEntity.badRequest().body(error);
        }
        
        if (win.isReceived()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Ce gain a déjà été marqué comme reçu");
            error.put("message", "Ce gain a déjà été marqué comme reçu");
            return ResponseEntity.badRequest().body(error);
        }

        win.setReceived(true);
        win.setReceivedAt(LocalDateTime.now());
        winRepository.save(win);

        Map<String, Object> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "Le gain a été marqué comme reçu");
        response.put("receivedAt", win.getReceivedAt());
        return ResponseEntity.ok(response);
    }
}