package de.avatic.lcc.service.access;

import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO;
import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.db.rates.ContainerRate;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer;
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
import de.avatic.lcc.util.exception.base.InternalErrorException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * Service class for managing container rates, including retrieval, listing, and
 * transformation of container rate data objects. Provides methods to interact with
 * repositories and handle business logic for container rates.
 */
@Service
public class ContainerRateService {

    private final ValidityPeriodRepository validityPeriodRepository;
    private final ContainerRateRepository containerRateRepository;
    private final ValidityPeriodTransformer validityPeriodTransformer;
    private final NodeTransformer nodeTransformer;
    private final NodeRepository nodeRepository;

    public ContainerRateService(ValidityPeriodRepository validityPeriodRepository, ContainerRateRepository containerRateRepository, ValidityPeriodTransformer validityPeriodTransformer, NodeTransformer nodeTransformer, NodeRepository nodeRepository) {
        this.validityPeriodRepository = validityPeriodRepository;
        this.containerRateRepository = containerRateRepository;
        this.validityPeriodTransformer = validityPeriodTransformer;
        this.nodeTransformer = nodeTransformer;
        this.nodeRepository = nodeRepository;
    }

    /**
     * Retrieves a paginated list of container rates filtered by the specified validity date.
     *
     * @param limit   the maximum number of results to retrieve per page
     * @param page    the current page number to retrieve
     * @param validAt the date for which to find the validity period
     * @return a {@link SearchQueryResult} containing container rate DTOs with pagination information
     */
    @Transactional
    public SearchQueryResult<ContainerRateDTO> listRates(String filter, int limit, int page, LocalDateTime validAt) {
        Optional<Integer> periodId = validityPeriodRepository.getPeriodId(validAt);
        return listRates(filter, limit, page, periodId.orElseThrow());
    }

    /**
     * Retrieves a paginated list of container rates filtered by a specific validity period ID.
     *
     * @param limit    the maximum number of results to retrieve per page
     * @param page     the current page number to retrieve
     * @param periodId the ID of the validity period to filter the rates by
     * @return a {@link SearchQueryResult} containing container rate DTOs with pagination information
     */
    @Transactional
    public SearchQueryResult<ContainerRateDTO> listRates(String filter, int limit, int page, Integer periodId) {
        if(null == periodId)
            return listRates(filter,limit, page);

        return SearchQueryResult.map(containerRateRepository.listRatesByPeriodId(filter, new SearchQueryPagination(page,limit), periodId), this::toContainerRateDTO);
    }

    /**
     * Retrieves a paginated list of all container rates without applying a specific filter.
     * It retrieves data for the currently valid validity period.
     *
     * @param limit the maximum number of results to retrieve per page
     * @param page  the current page number to retrieve
     * @return a {@link SearchQueryResult} containing container rate DTOs with pagination information
     */
    @Transactional
    public SearchQueryResult<ContainerRateDTO> listRates(String filter, int limit, int page) {
        var data = validityPeriodRepository.getValidPeriodId().map(id -> containerRateRepository.listRatesByPeriodId(filter, new SearchQueryPagination(page, limit), id)).orElseThrow(() -> new InternalErrorException("No validity period that is VALID"));
        return SearchQueryResult.map(data, this::toContainerRateDTO);
    }

    /**
     * Retrieves a container rate by its unique identifier.
     *
     * @param id the unique identifier of the container rate
     * @return a {@link ContainerRateDTO} containing the container rate details
     */
    @Transactional
    public ContainerRateDTO getContainerRate(Integer id) {
        return toContainerRateDTO(containerRateRepository.getById(id).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.CONTAINER_RATE, "id", String.valueOf(id))));
    }

    /**
     * Transforms a {@link ContainerRate} entity into a {@link ContainerRateDTO}.
     *
     * @param entity the {@link ContainerRate} entity to transform
     * @return a {@link ContainerRateDTO} containing the transformed data
     */
    private ContainerRateDTO toContainerRateDTO(ContainerRate entity) {
        ContainerRateDTO dto = new ContainerRateDTO();

        dto.setId(entity.getId());
        dto.setLeadTime(entity.getLeadTime());
        dto.setType(TransportType.valueOf(entity.getType().name()));
        dto.setValidityPeriod(validityPeriodTransformer.toValidityPeriodDTO(validityPeriodRepository.getById(entity.getValidityPeriodId())));
        dto.setLeadTime(entity.getLeadTime());
        dto.setSource(nodeTransformer.toNodeDTO(nodeRepository.getById(entity.getFromNodeId()).orElseThrow()));
        dto.setDestination(nodeTransformer.toNodeDTO(nodeRepository.getById(entity.getToNodeId()).orElseThrow()));

        var rates = new HashMap<String, Number>();
        rates.put(ContainerType.HC.name(), entity.getRateHc());
        rates.put(ContainerType.FEU.name(), entity.getRateFeu());
        rates.put(ContainerType.TEU.name(), entity.getRateTeu());

        dto.setRates(rates);

        return dto;
    }
}
