CodeController.java

package com.archiweb.api;

import com.archiweb.dto.CheckCodeRequest;
import com.archiweb.dto.CreateCodeRequest;
import com.archiweb.dto.UpdateCodeRequest;
import com.archiweb.model.Code;
import com.archiweb.model.PrizeType;
import com.archiweb.repository.CodeRepository;
import com.archiweb.service.CodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/api/codes")
@Tag(
        name = "Codes",
        description = """
        Module de gestion et de vérification des codes du jeu concours.
        Ce contrôleur permet :
        - la **vérification** d’un code client (valide, expiré, déjà utilisé, etc.),
        - la **création** et la **mise à jour** de codes par un administrateur,
        - la **consultation** des codes existants (filtrés et paginés),
        - la **suppression** des codes obsolètes.
        """
)
public class CodeController {

    private final CodeRepository repo;
    private final CodeService codeService;

    public CodeController(CodeRepository repo, CodeService codeService) {
        this.repo = repo;
        this.codeService = codeService;
    }

    // ================================================================
    // Vérification du service
    // ================================================================
    @GetMapping("/health")
    @Operation(
            summary = "Vérifier l'état du service Codes",
            description = "Permet de tester la disponibilité du module Codes. Renvoie une réponse 200 si le service est actif.",
            responses = {
                    @ApiResponse(responseCode = "200", description = "Service opérationnel")
            }
    )
    public String ping() {
        return "API Codes opérationnelle ✅";
    }

    // ================================================================
    // Vérification de validité d’un code
    // ================================================================
    @PostMapping("/check")
    @Operation(
            summary = "Vérifier la validité d’un code",
            description = """
            Vérifie la validité d’un code saisi par un utilisateur.
            Le service renvoie des informations précises selon l’état du code :
            - **200 OK** : code valide → gain associé retourné.
            - **409 Conflict** : code déjà utilisé.
            - **410 Gone** : code expiré.
            - **404 Not Found** : code inconnu.
            - **400 Bad Request** : code vide ou invalide.
            """,
            requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    required = true,
                    description = "Requête contenant la valeur du code à vérifier",
                    content = @Content(
                            schema = @Schema(implementation = CheckCodeRequest.class),
                            examples = @ExampleObject(value = """
                                    {
                                      "valeur": "CODE123ABC"
                                    }
                                    """)
                    )
            ),
            responses = {
                    @ApiResponse(responseCode = "200", description = "Code valide et gain associé",
                            content = @Content(
                                    mediaType = "application/json",
                                    examples = @ExampleObject(value = """
                                            {
                                              "status": "success",
                                              "message": "Félicitations, vous avez gagné : INFUSEUR",
                                              "valid": true,
                                              "prizeType": "INFUSEUR"
                                            }
                                            """)
                            )),
                    @ApiResponse(responseCode = "404", description = "Code inconnu"),
                    @ApiResponse(responseCode = "409", description = "Code déjà utilisé"),
                    @ApiResponse(responseCode = "410", description = "Code expiré"),
                    @ApiResponse(responseCode = "400", description = "Code vide ou invalide")
            }
    )
    public ResponseEntity<Map<String, Object>> checkCode(@RequestBody CheckCodeRequest request) {
        String valeur = request.getValeur();

        if (valeur == null || valeur.trim().isEmpty()) {
            Map<String, Object> resp = new HashMap<>();
            resp.put("status", "error");
            resp.put("message", "Code vide ou invalide.");
            resp.put("valid", false);
            return ResponseEntity.badRequest().body(resp);
        }

        return repo.findByValeur(valeur.trim().toUpperCase())
                .map(code -> {
                    Map<String, Object> resp = new HashMap<>();

                    if (code.getExpiresAt() != null && code.getExpiresAt().isBefore(LocalDateTime.now())) {
                        resp.put("status", "expired");
                        resp.put("message", "Ce code est expiré.");
                        resp.put("valid", false);
                        return ResponseEntity.status(HttpStatus.GONE).body(resp);
                    }

                    if (code.isUsed()) {
                        resp.put("status", "used");
                        resp.put("message", "Ce code a déjà été utilisé.");
                        resp.put("valid", false);
                        return ResponseEntity.status(HttpStatus.CONFLICT).body(resp);
                    }

                    resp.put("status", "success");
                    resp.put("message", "Félicitations, vous avez gagné : " + code.getPrizeType());
                    resp.put("valid", true);
                    resp.put("prizeType", code.getPrizeType().name());
                    return ResponseEntity.ok(resp);
                })
                .orElseGet(() -> {
                    Map<String, Object> resp = new HashMap<>();
                    resp.put("status", "error");
                    resp.put("message", "Code inconnu.");
                    resp.put("valid", false);
                    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(resp);
                });
    }

    // ================================================================
    // Récupérer un code spécifique
    // ================================================================
    @GetMapping("/{valeur}")
    @Operation(
            summary = "Obtenir un code spécifique",
            description = """
            Permet d’obtenir les informations détaillées d’un code à partir de sa valeur.
            Cet endpoint est principalement utilisé par les administrateurs ou le support.
            """,
            responses = {
                    @ApiResponse(responseCode = "200", description = "Code trouvé avec succès",
                            content = @Content(mediaType = "application/json",
                                    schema = @Schema(implementation = Code.class))),
                    @ApiResponse(responseCode = "404", description = "Code introuvable")
            }
    )
    public ResponseEntity<Object> getCode(@PathVariable String valeur) {
        return repo.findByValeur(valeur.toUpperCase())
                .<ResponseEntity<Object>>map(ResponseEntity::ok)
                .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(Map.of("message", "Code introuvable")));
    }

    // ================================================================
    // Lister les codes avec filtres
    // ================================================================
    @GetMapping
    @Operation(
            summary = "Lister les codes (paginés et filtrés)",
            description = """
            Renvoie la liste des codes existants avec pagination et filtres optionnels :
            - **prizeType** : type de lot (INFUSEUR, DETOX_100G…)
            - **used** : filtrer selon leur état d’utilisation
            - **page / size** : paramètres de pagination
            """,
            responses = {
                    @ApiResponse(responseCode = "200", description = "Liste des codes récupérée avec succès",
                            content = @Content(mediaType = "application/json",
                                    schema = @Schema(implementation = Page.class)))
            }
    )
    public ResponseEntity<Page<Code>> getCodes(
            @Parameter(description = "Numéro de page (par défaut 0)") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "Taille de la page (par défaut 20)") @RequestParam(defaultValue = "20") int size,
            @Parameter(description = "Filtrer par type de lot (ex: INFUSEUR)") @RequestParam(required = false) PrizeType prizeType,
            @Parameter(description = "Filtrer selon l’état d’utilisation du code") @RequestParam(required = false) Boolean used
    ) {
        Page<Code> result = codeService.getCodesFiltered(prizeType, used, page, size);
        return ResponseEntity.ok(result);
    }

    // ================================================================
    // Créer un nouveau code
    // ================================================================
    @PostMapping
    @Operation(
            summary = "Créer un nouveau code",
            description = """
            Ajoute un nouveau code dans la base de données.
            Si la valeur existe déjà, une erreur 409 est renvoyée.
            """,
            requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    required = true,
                    description = "Informations du code à créer",
                    content = @Content(
                            schema = @Schema(implementation = CreateCodeRequest.class),
                            examples = @ExampleObject(value = """
                                    {
                                      "valeur": "NEWCODE123",
                                      "prizeType": "INFUSEUR",
                                      "expiresAt": "2025-12-31T23:59:59"
                                    }
                                    """)
                    )
            ),
            responses = {
                    @ApiResponse(responseCode = "201", description = "Code créé avec succès"),
                    @ApiResponse(responseCode = "409", description = "Valeur de code déjà existante")
            }
    )
    public ResponseEntity<?> createCode(@RequestBody CreateCodeRequest req) {
        if (repo.findByValeur(req.getValeur()).isPresent()) {
            return ResponseEntity.status(HttpStatus.CONFLICT)
                    .body(Map.of("message", "Valeur déjà existante"));
        }

        Code code = new Code();
        code.setValeur(req.getValeur().toUpperCase());
        code.setPrizeType(req.getPrizeType());
        code.setExpiresAt(req.getExpiresAt() != null ? req.getExpiresAt() : LocalDateTime.now().plusDays(60));
        code.setUsed(false);
        code.setUsedAt(null);

        return ResponseEntity.status(HttpStatus.CREATED).body(repo.save(code));
    }

    // ================================================================
    // Mettre à jour un code existant
    // ================================================================
    @PutMapping("/{id}")
    @Operation(
            summary = "Mettre à jour un code existant",
            description = """
            Met à jour les informations d’un code existant :
            - type de lot
            - date d’expiration
            - état d’utilisation
            """,
            responses = {
                    @ApiResponse(responseCode = "200", description = "Code mis à jour avec succès"),
                    @ApiResponse(responseCode = "404", description = "Code introuvable")
            }
    )
    public ResponseEntity<?> updateCode(@PathVariable Long id, @RequestBody UpdateCodeRequest req) {
        return repo.findById(id)
                .map(existing -> {
                    existing.setPrizeType(req.getPrizeType());
                    existing.setExpiresAt(req.getExpiresAt());
                    existing.setUsed(req.isUsed());
                    existing.setUsedAt(req.getUsedAt());
                    repo.save(existing);
                    return ResponseEntity.ok(Map.of(
                            "message", "Code mis à jour avec succès",
                            "code", existing
                    ));
                })
                .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(Map.of("message", "Code introuvable")));
    }

    // ================================================================
    // Supprimer un code
    // ================================================================
    @DeleteMapping("/{id}")
    @Operation(
            summary = "Supprimer un code",
            description = "Supprime un code existant de la base de données en fonction de son identifiant unique.",
            responses = {
                    @ApiResponse(responseCode = "200", description = "Code supprimé avec succès"),
                    @ApiResponse(responseCode = "404", description = "Code introuvable")
            }
    )
    public ResponseEntity<?> deleteCode(@PathVariable Long id) {
        if (!repo.existsById(id)) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(Map.of("message", "Code introuvable"));
        }
        repo.deleteById(id);
        return ResponseEntity.ok(Map.of("message", "Code supprimé avec succès"));
    }
}