package de.avatic.lcc.controller.configuration;

import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO;
import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO;
import de.avatic.lcc.dto.configuration.rates.StagedRatesDTO;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.service.access.ContainerRateService;
import de.avatic.lcc.service.access.MatrixRateService;
import de.avatic.lcc.service.configuration.RateApprovalService;
import de.avatic.lcc.service.access.ValidityPeriodService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.constraints.Min;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/api/rates")
@Validated
public class RateController {

    private final MatrixRateService matrixRateService;
    private final ContainerRateService containerRateService;
    private final RateApprovalService rateApprovalService;
    private final ValidityPeriodService validityPeriodService;

    /**
     * Constructs a RateController instance with the specified services.
     *
     * @param matrixRateService the service responsible for handling matrix rate operations
     * @param containerRateService the service responsible for managing container rates
     * @param rateApprovalService the service responsible for rate approval processes
     * @param validityPeriodService the service responsible for managing validity period operations
     */
    public RateController(MatrixRateService matrixRateService, ContainerRateService containerRateService, RateApprovalService rateApprovalService, ValidityPeriodService validityPeriodService) {
        this.matrixRateService = matrixRateService;
        this.containerRateService = containerRateService;
        this.rateApprovalService = rateApprovalService;
        this.validityPeriodService = validityPeriodService;
    }

    /**
     * Retrieves a list of container rates based on the provided query parameters.
     *
     * @param limit the maximum number of container rates to return, defaults to 20 if not specified
     * @param page the page number to retrieve, defaults to 0 if not specified
     * @param validityPeriodId the ID of the validity period to filter container rates, optional
     * @param validAt the specific date and time to filter container rates, optional
     * @return a ResponseEntity containing the list of container rates and additional pagination headers
     */
    @GetMapping({"/container", "/container/" })
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<List<ContainerRateDTO>> listContainerRates(
            @RequestParam(defaultValue = "") String filter,
            @RequestParam(defaultValue = "20") @Min(1) int limit,
            @RequestParam(defaultValue = "1") @Min(1) int page,
            @RequestParam(name= "valid", required = false) Integer validityPeriodId,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validAt) {

        SearchQueryResult<ContainerRateDTO> containerRates = null;

        if(validAt != null) {
            containerRates = containerRateService.listRates(filter, limit, page, validAt);
        }
        else if(validityPeriodId != null) {
            containerRates = containerRateService.listRates(filter, limit, page, validityPeriodId);
        }
        else {
            containerRates = containerRateService.listRates(filter, limit, page);
        }

        return ResponseEntity.ok()
                .header("X-Total-Count", String.valueOf(containerRates.getTotalElements()))
                .header("X-Page-Count", String.valueOf(containerRates.getTotalPages()))
                .header("X-Current-Page", String.valueOf(page))
                .body(containerRates.toList());
    }

    /**
     * Retrieves the rate information for a container by its unique identifier.
     *
     * @param id the unique identifier of the container whose rate information is to be retrieved
     * @return a ResponseEntity containing the ContainerRateDTO with the rate information of the specified container
     */
    @GetMapping({"/container/{id}", "/container/{id}/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<ContainerRateDTO> getContainerRate(@PathVariable Integer id) {
        return ResponseEntity.ok(containerRateService.getContainerRate(id));
    }

    /**
     * Retrieves a list of matrix rates with optional filters.
     *
     * @param limit the maximum number of records to return (default is 20).
     * @param page the page index to retrieve, starting from 0 (default is 0).
     * @param valid an optional filter to retrieve records based on validity (null if not provided).
     * @param validAt an optional filter for retrieving records valid at a specific date and time (null if not provided).
     * @return a {@link ResponseEntity} containing a list of {@link MatrixRateDTO} wrapped in the response body,
     * including pagination headers.
     */
    @GetMapping({"/matrix","/matrix/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<List<MatrixRateDTO>> listMatrixRates(
            @RequestParam(defaultValue = "") String filter,
            @RequestParam(defaultValue = "20") @Min(1) int limit,
            @RequestParam(defaultValue = "1") @Min(1) int page,
            @RequestParam(required = false) Integer valid,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validAt) {

        SearchQueryResult<MatrixRateDTO> rates = null;

        if(validAt != null) {
            rates = matrixRateService.listRates(filter, limit, page, validAt);
        }
        else if(valid != null) {
            rates = matrixRateService.listRates(filter, limit, page, valid);
        }
        else {
            rates = matrixRateService.listRates(filter, limit, page);
        }

        return ResponseEntity.ok()
                .header("X-Total-Count", String.valueOf(rates.getTotalElements()))
                .header("X-Page-Count", String.valueOf(rates.getTotalPages()))
                .header("X-Current-Page", String.valueOf(page))
                .body(rates.toList());
    }

    /**
     * Retrieves the matrix rate for the specified ID.
     *
     * @param id the unique identifier of the matrix rate to retrieve
     * @return a ResponseEntity containing the MatrixRateDTO for the specified ID
     */
    @GetMapping({"/matrix/{id}", "/matrix/{id}/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<MatrixRateDTO> getMatrixRate(@PathVariable Integer id) {
        return ResponseEntity.ok(matrixRateService.getRate(id));
    }

    /**
     * Retrieves all validity periods of the application properties.
     *
     * @return ResponseEntity containing the list of ValidityPeriodDTO objects.
     */
    @GetMapping({"/periods", "/periods/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<List<ValidityPeriodDTO>> listPeriods() {
        return ResponseEntity.ok(validityPeriodService.listPeriods());
    }

    /**
     * Invalidates a specific validity period by its ID.
     *
     * @param id The ID of the validity period to invalidate.
     * @return ResponseEntity indicating the operation status.
     */
    @DeleteMapping({"/periods/{id}", "/periods/{id}/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<Void> invalidatePeriod(@PathVariable Integer id) {
        validityPeriodService.invalidate(id);
        return ResponseEntity.ok().build();
    }

    /**
     * Checks if there are any rate drafts in the rate approval service.
     *
     * @return ResponseEntity containing a Boolean value indicating
     *         whether rate drafts exist (true) or not (false).
     */
    @GetMapping( {"/staged_changes", "/staged_changes/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<StagedRatesDTO> checkRateDrafts() {
        return ResponseEntity.ok(rateApprovalService.getStagedRateDTO());
    }

    /**
     * Approves the staged rate drafts by invoking the rate approval service.
     *
     * @return ResponseEntity with HTTP 200 status if the operation is successful.
     */
    @PutMapping({"/staged_changes", "/staged_changes/"})
    @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')")
    public ResponseEntity<Void> approveRateDrafts() {
        rateApprovalService.approveRateDrafts();
        return ResponseEntity.ok().build();
    }
}
