package ru.yandex.canvas.controllers;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import ru.yandex.canvas.configs.auth.AuthorizeBy;
import ru.yandex.canvas.model.CreativeDocument;
import ru.yandex.canvas.model.ValidationError;
import ru.yandex.canvas.model.direct.CreativeUploadData;
import ru.yandex.canvas.service.AuthService;
import ru.yandex.canvas.service.CreativesService;
import ru.yandex.canvas.service.DirectService;
import ru.yandex.canvas.service.PresetsService;
import ru.yandex.canvas.service.direct.CreativeUploadHelperFacade;
import ru.yandex.canvas.service.html5.Html5Preset;
import ru.yandex.canvas.service.video.VideoPresetsService;

import static java.util.concurrent.CompletableFuture.runAsync;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static ru.yandex.canvas.Html5Constants.ALL_HTML5_PRESETS;
import static ru.yandex.canvas.configs.auth.AuthorizeBy.AuthType.DIRECT_TOKEN;
import static ru.yandex.direct.utils.CommonUtils.nvl;


/**
 * @author skirsanov
 */
@RestController
@RequestMapping(path = "/direct")
public class DirectController {
    public static final int MAX_CREATIVES_COUNT = 50;
    private static final Logger logger = LoggerFactory.getLogger(DirectController.class);
    private final DirectService directService;
    private final CreativesService creativesService;
    private final Executor executor;
    private final AuthService authService;
    private final VideoPresetsService videoPresetsService;
    private final PresetsService canvasPresetsService;
    private final CreativeUploadHelperFacade creativeUploadHelperFacade;


    public DirectController(final DirectService directService,
                            final CreativesService creativesService, Executor executor,
                            AuthService authService, VideoPresetsService videoPresetsService,
                            PresetsService canvasPresetsService,
                            CreativeUploadHelperFacade creativeUploadHelperFacade) {
        this.directService = directService;
        this.creativesService = creativesService;
        this.executor = executor;
        this.authService = authService;
        this.videoPresetsService = videoPresetsService;
        this.canvasPresetsService = canvasPresetsService;
        this.creativeUploadHelperFacade = creativeUploadHelperFacade;
    }

    @PostMapping(path = "creatives")
    public ResponseEntity<?> sendToDirect(@Valid @RequestBody final DirectData directData,
                                          final BindingResult bindingResult,
                                          @RequestParam("client_id") long clientId) {

        if (bindingResult.hasErrors()) {
            logger.warn(bindingResult.getAllErrors().toString());
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ValidationError("Invalid request"));
        }

        final List<CreativeDocument> creatives =
                creativesService.getList(directData.getBatches()
                                .stream()
                                .collect(Collectors.toMap(DirectData.BatchData::getId,
                                        DirectData.BatchData::getCreativeIds)),
                        clientId);

        long userId = authService.getUserId();

        CompletableFuture.allOf(
                runAsync(() -> creativesService.exportToRTBHost(creatives), executor),
                runAsync(() -> directService.sendCreatives(userId, clientId, creatives, creativeUploadHelperFacade),
                        executor))
                .join(); // IGNORE-BAD-JOIN DIRECT-149116

        return ResponseEntity.ok(directData);
    }

    @AuthorizeBy({DIRECT_TOKEN})
    @GetMapping(path = "creatives")
    public ResponseEntity<?> getList(@Valid @RequestParam("ids") @NotNull final Collection<Long> ids,
                                     @RequestParam("client_id") long clientId) {
        if (ids.isEmpty() || ids.size() > MAX_CREATIVES_COUNT) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ValidationError("Invalid request"));
        }

        List<CreativeDocument> creativeDocuments =
                creativesService.getCreatives(ids, clientId);

        final Map<Long, CreativeUploadData> creatives = creativeDocuments.stream()
                .map(creative -> creativeUploadHelperFacade.toCreativeUploadData(creative, clientId))
                .collect(toMap(CreativeUploadData::getCreativeId, Function.identity()));

        return ResponseEntity.ok(ids.stream().map(id -> {
            boolean ok = creatives.containsKey(id);
            return new CreativeResponse(id, ok, !ok ? "Not Found" : null, creatives.get(id));
        }).collect(toList()));
    }


    private static class CreativeResponse {
        private long creativeId;
        private boolean ok;
        private String message;
        @JsonProperty("creative")
        private CreativeUploadData creativeUploadData;

        public CreativeResponse(long creativeId, boolean ok, String message, CreativeUploadData creativeUploadData) {
            this.creativeId = creativeId;
            this.ok = ok;
            this.message = message;
            this.creativeUploadData = creativeUploadData;
        }

        public long getCreativeId() {
            return creativeId;
        }

        public void setCreativeId(long creativeId) {
            this.creativeId = creativeId;
        }

        public boolean isOk() {
            return ok;
        }

        public void setOk(boolean ok) {
            this.ok = ok;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public CreativeUploadData getCreativeUploadData() {
            return creativeUploadData;
        }

        public void setCreativeUploadData(CreativeUploadData creativeUploadData) {
            this.creativeUploadData = creativeUploadData;
        }
    }


    private static class DirectData {
        @NotNull
        @Size(min = 1)
        private List<BatchData> batches;

        @NotNull
        public List<BatchData> getBatches() {
            return batches;
        }

        public void setBatches(@NotNull List<BatchData> batches) {
            this.batches = batches;
        }

        @Override
        public String toString() {
            return "DirectData{" +
                    "batches=" + batches +
                    '}';
        }

        private static class BatchData {
            private String id;
            @NotNull
            @Size(min = 1)
            private List<Long> creativeIds;

            public String getId() {
                return id;
            }

            public void setId(String id) {
                this.id = id;
            }

            @NotNull
            public List<Long> getCreativeIds() {
                return creativeIds;
            }

            public void setCreativeIds(@NotNull List<Long> creativeIds) {
                this.creativeIds = creativeIds;
            }

            @Override
            public String toString() {
                return "BatchData{" +
                        "id=" + id +
                        ", creativeIds=" + creativeIds +
                        '}';
            }
        }
    }

    @AuthorizeBy({DIRECT_TOKEN})
    @GetMapping(path = "presets")
    public ResponseEntity<?> getPresets() {
        var videoPresets = videoPresetsService.getPresets().stream()
                .filter(e -> !nvl(e.getDescription().getHiddenFromList(), false))
                .map(it -> new Preset(it.getName(), it.getId(), false))
                .collect(toList());
        var canvasPresets = canvasPresetsService.getAll().stream()
                .map(it -> new Preset(it.getName(), it.getId().longValue(),
                        it.getItems().stream().anyMatch(item -> item.getOptions() != null
                                && Boolean.TRUE.equals(item.getOptions().getIsAdaptive()))))
                .collect(toList());

        var html5Presets = ALL_HTML5_PRESETS.stream()
                .sorted(Comparator.comparing(Html5Preset::getSize))
                .map(it -> new Preset(it.getName(), (long) it.getId(), false))
                .collect(toList());

        var response = new PresetsResponse(videoPresets, canvasPresets, html5Presets);
        return ResponseEntity.ok(response);
    }

    private static class PresetsResponse {
        private final List<Preset> videoPresets;
        private final List<Preset> canvasPresets;
        private final List<Preset> html5Presets;

        public PresetsResponse(List<Preset> videoPresets, List<Preset> canvasPresets,
                               List<Preset> html5Presets) {
            this.videoPresets = videoPresets;
            this.canvasPresets = canvasPresets;
            this.html5Presets = html5Presets;
        }

        public List<Preset> getVideoPresets() {
            return videoPresets;
        }

        public List<Preset> getCanvasPresets() {
            return canvasPresets;
        }

        public List<Preset> getHtml5Presets() {
            return html5Presets;
        }
    }

    private static class Preset {
        private final String name;
        private final Long id;
        private final Boolean isAdaptive;

        public Preset(String name, Long id, Boolean isAdaptive) {
            this.name = name;
            this.id = id;
            this.isAdaptive = isAdaptive;
        }

        public String getName() {
            return name;
        }

        public Long getId() {
            return id;
        }

        public Boolean getIsAdaptive() {
            return isAdaptive;
        }
    }
}
