package io.delphiplatform.api.v3.view;

import org.openapitools.jackson.nullable.JsonNullable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ResponseStatusException;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import javax.transaction.NotSupportedException;

import io.delphiplatform.api.exception.InvalidParameterException;
import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.util.OffsetBasedPageRequest;
import io.delphiplatform.api.v3.bigtable.BigtableTrackPositionAdditionsPublicService;
import io.delphiplatform.api.v3.bigtable.BigtableTrackPositionCurrentTracklistPublicService;
import io.delphiplatform.api.v3.bigtable.BigtableTrackPositionPreviousPublicService;
import io.delphiplatform.api.v3.bigtable.BigtableTrackPositionPublicService;
import io.delphiplatform.api.v3.bigtable.BigtableTrackPositionSummaryPublicService;
import io.delphiplatform.api.v3.dto.ModelsListWithMetadataDto;
import io.delphiplatform.api.v3.model.PlaylistPublicPositionsDate;
import io.delphiplatform.api.v3.model.PlaylistPublicPositionsDates;
import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.model.trackposition.PlaylistsTrackPositionsCurrentTracklist;
import io.delphiplatform.api.v3.model.trackposition.PlaylistsTrackPositionsCurrentTracklists;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPosition;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositionAddition;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositionAdditions;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositionSummaries;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositionSummary;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositions;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositionsMeta;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPreviousPosition;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPreviousPositions;
import io.delphiplatform.api.v3.view.util.Params;
import io.delphiplatform.api.v3.view.util.ParamsHelper;
import io.delphiplatform.api.v3.view.util.SortWithOrder;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("${spring.data.rest.base-path:/v3}")
@Slf4j
public class TrackPositionsApiPublicController implements TrackPositionsPublicApi {

    public static final int PREVIOUS_PLAYLISTS_MAX_ISRC_LIST_SIZE = 10;

    private final ParamsHelper paramsHelper;
    private final BigtableTrackPositionPublicService trackPositionService;
    private final BigtableTrackPositionAdditionsPublicService trackPositionAdditionsService;
    private final BigtableTrackPositionPreviousPublicService trackPositionPreviousService;
    private final BigtableTrackPositionCurrentTracklistPublicService trackPositionCurrentTracklistService;
    private final BigtableTrackPositionSummaryPublicService trackPositionSummaryService;

    public TrackPositionsApiPublicController(
        ParamsHelper paramsHelper,
        BigtableTrackPositionPublicService trackPositionService,
        BigtableTrackPositionAdditionsPublicService trackPositionAdditionsService,
        BigtableTrackPositionPreviousPublicService trackPositionPreviousService,
        BigtableTrackPositionCurrentTracklistPublicService trackPositionCurrentTracklistService,
        BigtableTrackPositionSummaryPublicService trackPositionSummaryService
    ) {
        this.paramsHelper = paramsHelper;
        this.trackPositionService = trackPositionService;
        this.trackPositionAdditionsService = trackPositionAdditionsService;
        this.trackPositionPreviousService = trackPositionPreviousService;
        this.trackPositionCurrentTracklistService = trackPositionCurrentTracklistService;
        this.trackPositionSummaryService = trackPositionSummaryService;
    }

    @Override
    public CompletableFuture<ResponseEntity<PublicPlaylistTrackPositions>> getPlaylistTrackPositions(Params params) {
        try {
            CompletableFuture<ModelsListWithMetadataDto<PublicPlaylistTrackPosition, PublicPlaylistTrackPositionsMeta>> positionsWithMeta
                = trackPositionService.getPlaylistTrackPositionModels(params);

            return positionsWithMeta.thenApply(positionsListDTO -> {
                List<PublicPlaylistTrackPosition> positions = positionsListDTO.getModels()
                    .collect(Collectors.toList());
                PublicPlaylistTrackPositionsMeta meta = positionsListDTO.getMeta();

                return ResponseEntity.ok(new PublicPlaylistTrackPositions()
                    .items(positions)
                    .meta(meta)
                    .nextCursor(params.getNextCursor())
                    .count(positions.size()));
            });
        } catch (NotSupportedException e) {
            log.error("Error while getting playlist positions.", e);
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }

    @Override
    public CompletableFuture<ResponseEntity<PublicPlaylistTrackPositionSummaries>> getPlaylistTrackPositionsSummaries(
        Params params) {
        try {
            CompletableFuture<List<PublicPlaylistTrackPositionSummary>> models = trackPositionSummaryService
                .getPlaylistTrackPositionsSummaryModels(params);
            return models.thenApply(r -> ResponseEntity.ok(PublicPlaylistTrackPositionSummaries.builder()
                .items(r)
                .nextCursor(JsonNullable.of(params.getNextCursor()))
                .count(r.size())
                .build()));
        } catch (NotSupportedException e) {
            log.error("Error while getting playlist position summaries.", e);
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }

    @Override
    public CompletableFuture<ResponseEntity<PublicPlaylistTrackPositionAdditions>> getPlaylistTrackPositionsAdditions(
        Params params) {
        CompletableFuture<List<PublicPlaylistTrackPositionAddition>> models;
        try {
            models = trackPositionAdditionsService
                .getPlaylistTrackPositionAdditionModels(params);
        } catch (NotSupportedException e) {
            log.error("Error while getting playlist position summaries.", e);
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
        return models.thenApply(r -> ResponseEntity.ok(new PublicPlaylistTrackPositionAdditions()
            .items(r)
            .nextCursor(params.getNextCursor())
            .count(r.size())));
    }

    @Override
    public CompletableFuture<ResponseEntity<PublicPlaylistTrackPreviousPositions>> trackPositionsSearchPreviousPlaylists(Params params) {
        if (params.getIsrc().size() > PREVIOUS_PLAYLISTS_MAX_ISRC_LIST_SIZE) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Max 'isrc' list size is " + PREVIOUS_PLAYLISTS_MAX_ISRC_LIST_SIZE);
        }

        try {
            CompletableFuture<ModelsListWithMetadataDto<PublicPlaylistTrackPreviousPosition, PublicPlaylistTrackPositionsMeta>>
                previousPositionsWithMeta = trackPositionPreviousService.getPlaylistTrackPreviousPositionsModels(params);

            return previousPositionsWithMeta.thenApply(prevPositionsListDTO -> {
                List<PublicPlaylistTrackPreviousPosition> prevPositions = prevPositionsListDTO.getModels()
                    .collect(Collectors.toList());
                PublicPlaylistTrackPositionsMeta meta = prevPositionsListDTO.getMeta();

                return ResponseEntity.ok(new PublicPlaylistTrackPreviousPositions(prevPositions, meta));
            });
        } catch (NotSupportedException e) {
            log.error("Error while getting playlist previous positions.", e);
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }

    @Override
    public CompletableFuture<ResponseEntity<PlaylistsTrackPositionsCurrentTracklists>> getCurrentTracklist(Params params) {
        try {
            CompletableFuture<List<PlaylistsTrackPositionsCurrentTracklist>> models = trackPositionCurrentTracklistService
                .getPlaylistCurrentTrackPositions(params);
            return models.thenApply(r -> ResponseEntity.ok(new PlaylistsTrackPositionsCurrentTracklists()
                .items(r)
                .nextCursor(params.getNextCursor())
                .count(r.size())));
        } catch (NotSupportedException e) {
            log.error("Error while getting playlist positions.", e);
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }


    @Override
    public ResponseEntity<PlaylistPublicPositionsDates> getPlaylistsTrackPositionsDates(String playlistId,
        Set<String> countryCodes, String sortBy, SortOrder sortOrder) {
        try {
            List<PlaylistPublicPositionsDate> items = trackPositionService
                .findTrackPositionsDates(playlistId,
                    countryCodes,
                    OffsetBasedPageRequest.ofParamsToCamelCase(null, null, sortOrder, sortBy));
            return ResponseEntity.ok(new PlaylistPublicPositionsDates().items(items).count(items.size()));
        } catch (NotSupportedException e) {
            log.error("Error while getting playlist position dates.", e);
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }

    @Override
    public void validateParams(Params params) {
        String msg = null;

        if (CollectionUtils.isEmpty(params.getArtistId())
            && CollectionUtils.isEmpty(params.getPlaylistId())
            && CollectionUtils.isEmpty(params.getChartId())
            && CollectionUtils.isEmpty(params.getIsrc())) {
            msg = "Missing at least one identifiying parameter from set: [artist_id, playlist_id, isrc]";
        }

        if (msg != null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, msg);
        }
    }

    @Override
    public Params loadAdditionalParams(Params params) {
        return paramsHelper.loadAdditionalParamsForTrackPositions(params);
    }

    @Override
    public List<SortWithOrder> parseSortList(List<String> sortByList, List<SortOrder> sortOrderList) {
        if (CollectionUtils.isEmpty(sortByList)) {
            return Collections.emptyList();
        }

        if (sortByList.size() != sortOrderList.size()) {
            throw new InvalidParameterException("Number of passed 'sort_by' values and 'sort_order' values must be equal");
        }

        return paramsHelper.parseSortingLists(sortByList, sortOrderList);
    }
}
