TariffV2QueryController.java

package com.github.jenkaby.bikerental.tariff.web.query;

import com.github.jenkaby.bikerental.shared.config.OpenApiConfig;
import com.github.jenkaby.bikerental.shared.domain.model.vo.Page;
import com.github.jenkaby.bikerental.shared.domain.model.vo.PageRequest;
import com.github.jenkaby.bikerental.shared.web.support.Id;
import com.github.jenkaby.bikerental.tariff.application.usecase.GetActiveTariffsV2ByEquipmentTypeUseCase;
import com.github.jenkaby.bikerental.tariff.application.usecase.GetAllTariffsV2UseCase;
import com.github.jenkaby.bikerental.tariff.application.usecase.GetTariffV2ByIdUseCase;
import com.github.jenkaby.bikerental.tariff.application.usecase.SelectTariffV2UseCase;
import com.github.jenkaby.bikerental.tariff.domain.model.TariffV2;
import com.github.jenkaby.bikerental.tariff.web.query.dto.TariffSelectionV2Response;
import com.github.jenkaby.bikerental.tariff.web.query.dto.TariffV2Response;
import com.github.jenkaby.bikerental.tariff.web.query.mapper.TariffV2QueryMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.time.Duration;
import java.time.LocalDate;
import java.util.List;

@Validated
@Slf4j
@RestController
@RequestMapping("/api/v2/tariffs")
@Tag(name = OpenApiConfig.Tags.TARIFFS, description = "Tariff V2 API")
public class TariffV2QueryController {

    private final GetTariffV2ByIdUseCase getByIdUseCase;
    private final GetAllTariffsV2UseCase getAllUseCase;
    private final GetActiveTariffsV2ByEquipmentTypeUseCase getActiveByTypeUseCase;
    private final SelectTariffV2UseCase selectTariffUseCase;
    private final TariffV2QueryMapper mapper;

    TariffV2QueryController(GetTariffV2ByIdUseCase getByIdUseCase,
                            GetAllTariffsV2UseCase getAllUseCase,
                            GetActiveTariffsV2ByEquipmentTypeUseCase getActiveByTypeUseCase,
                            SelectTariffV2UseCase selectTariffUseCase,
                            TariffV2QueryMapper mapper) {
        this.getByIdUseCase = getByIdUseCase;
        this.getAllUseCase = getAllUseCase;
        this.getActiveByTypeUseCase = getActiveByTypeUseCase;
        this.selectTariffUseCase = selectTariffUseCase;
        this.mapper = mapper;
    }

    @GetMapping("/{id}")
    @Operation(summary = "Get V2 tariff by ID")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Tariff found",
                    content = @Content(schema = @Schema(implementation = TariffV2Response.class))),
            @ApiResponse(responseCode = "404", description = "Tariff not found",
                    content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
    })
    public ResponseEntity<TariffV2Response> getTariffById(
            @Parameter(description = "Tariff ID") @PathVariable("id") @Id Long id) {
        log.info("[GET] Get V2 tariff by id {}", id);
        TariffV2 tariff = getByIdUseCase.get(id);
        return ResponseEntity.ok(mapper.toResponse(tariff));
    }

    @GetMapping
    @Operation(summary = "Get all V2 tariffs (paginated)")
    @ApiResponses(@ApiResponse(responseCode = "200", description = "Tariff page returned"))
    public ResponseEntity<Page<TariffV2Response>> getAllTariffs(
            @PageableDefault(size = 20, sort = "name", direction = Sort.Direction.ASC) Pageable pageable) {
        log.info("[GET] Get all V2 tariffs with pagination");
        PageRequest pageRequest = new PageRequest(
                pageable.getPageSize(),
                pageable.getPageNumber(),
                pageable.getSort().toString()
        );
        Page<TariffV2> tariffs = getAllUseCase.execute(pageRequest);
        Page<TariffV2Response> response = tariffs.map(mapper::toResponse);
        return ResponseEntity.ok(response);
    }

    @GetMapping("/active")
    @Operation(summary = "Get active V2 tariffs by equipment type")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Active tariffs returned",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = TariffV2Response.class))))
    })
    public ResponseEntity<List<TariffV2Response>> getActiveTariffs(
            @Parameter(description = "Equipment type slug") @RequestParam(value = "equipmentType") String equipmentType) {
        log.info("[GET] Get active V2 tariffs for equipment type {}", equipmentType);
        List<TariffV2> tariffs = getActiveByTypeUseCase.execute(equipmentType);
        return ResponseEntity.ok(tariffs.stream().map(mapper::toResponse).toList());
    }

    @GetMapping("/selection")
    @Operation(summary = "Select cheapest V2 tariff for equipment type and duration. For debug purpose")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Tariff selected",
                    content = @Content(schema = @Schema(implementation = TariffSelectionV2Response.class))),
            @ApiResponse(responseCode = "404", description = "No suitable tariff found",
                    content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
    })
    public ResponseEntity<TariffSelectionV2Response> selectTariff(
            @Parameter(description = "Equipment type") @RequestParam("equipmentType") @NotBlank String equipmentType,
            @Parameter(description = "Duration in minutes") @RequestParam("durationMinutes") @Positive int durationMinutes,
            @Parameter(description = "Rental date (default: today)")
            @RequestParam(value = "rentalDate", required = false)
            @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate rentalDate) {
        log.info("[GET] Select V2 tariff for equipmentType={}, durationMinutes={}", equipmentType, durationMinutes);
        var duration = Duration.ofMinutes(durationMinutes);
        var tariff = selectTariffUseCase.execute(new SelectTariffV2UseCase.SelectTariffCommand(equipmentType, duration, rentalDate));
        var cost = tariff.calculateCost(duration);
        var response = new TariffSelectionV2Response(
                mapper.toResponse(tariff),
                cost.totalCost().amount(),
                cost.calculationBreakdown()
        );
        return ResponseEntity.ok(response);
    }
}