package ru.yandex.canvas.controllers;

import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 org.springframework.web.server.UnsupportedMediaTypeStatusException;

import ru.yandex.canvas.configs.auth.AuthorizeBy;
import ru.yandex.canvas.exceptions.BadRequestException;
import ru.yandex.canvas.exceptions.InternalServerError;
import ru.yandex.canvas.exceptions.NotFoundException;
import ru.yandex.canvas.model.direct.Privileges;
import ru.yandex.canvas.model.video.files.FileStatus;
import ru.yandex.canvas.model.video.vc.feed.VideoConstructorFeedCropParams;
import ru.yandex.canvas.model.video.vc.files.VideoConstructorFile;
import ru.yandex.canvas.model.video.vc.files.VideoConstructorFiles;
import ru.yandex.canvas.service.AuthService;
import ru.yandex.canvas.service.SandBoxService;
import ru.yandex.canvas.service.video.VideoConstructorFilesService;
import ru.yandex.canvas.service.video.VideoPresetsService;
import ru.yandex.canvas.service.video.presets.VideoPreset;

import static ru.yandex.canvas.configs.auth.AuthorizeBy.AuthType.SANDBOX_SECRET;

@RestController
@RequestMapping(value = "/video/constructor-files")
public class VideoConstructorFilesController {
    private final AuthService authService;
    private final VideoConstructorFilesService videoConstructorFilesService;
    private final VideoPresetsService videoPresetsService;

    public VideoConstructorFilesController(AuthService authService,
                                           VideoConstructorFilesService userFileService,
                                           VideoPresetsService videoPresetsService) {
        this.authService = authService;
        this.videoConstructorFilesService = userFileService;
        this.videoPresetsService = videoPresetsService;
    }

    @PostMapping
    public ResponseEntity<VideoConstructorFile> uploadFile(@RequestParam("client_id") final long clientId,
                                                           @RequestBody final byte[] data) {
        authService.requirePermission(Privileges.Permission.CREATIVE_CREATE);

        // Временное решение!, после выхода фронта в прод надо оставить только CreateVideoConstructorFileRequest,
        // без парсинга вручную.
        ObjectMapper objectMapper = new ObjectMapper();
        VideoConstructorFile file;
        CreateVideoConstructorFileRequest request;
        List<VideoConstructorFeedCropParams> cropParams = null;
        try {
            file = objectMapper.readValue(data, VideoConstructorFile.class);
        } catch (Exception e) {
            try {
                request = objectMapper.readValue(data, CreateVideoConstructorFileRequest.class);
                file = request.getVideoConstructorFile();
                cropParams = request.getCropParams();
            } catch (Exception ex) {
                throw new UnsupportedMediaTypeStatusException("Can't parse request");
            }
        }

        Date timeNow = java.sql.Date.from(Instant.now());
        if (file.getId() == null) {
            file.withCreationTime(timeNow);
        }
        file.withArchive(false)
                .withDate(timeNow)
                .withUserId(authService.getUserId())
                .withClientId(clientId);
        return ResponseEntity.ok(videoConstructorFilesService.upload(file, cropParams));
    }

    @GetMapping(value = "my")
    public VideoConstructorFiles getMyFiles(@RequestParam("client_id") final long clientId,
            @RequestParam(value = "name", required = false) String name,
            @RequestParam(value = "aspect_ratio_width", required = false) final Integer aspectRatioWidth,
            @RequestParam(value = "aspect_ratio_height", required = false) final Integer aspectRatioHeight,
            @RequestParam(value = "duration_from", required = false) final Double durationFrom,
            @RequestParam(value = "duration_to", required = false) final Double durationTo,
            @RequestParam(value = "offset", defaultValue = "0") int offset,
            @RequestParam(value = "limit", defaultValue = "24") int limit,
            @RequestParam(value = "has_audio", required = false) Boolean hasAudio,
            @RequestParam(value = "sort_order", defaultValue = "desc", required = false) final String sortOrder,
            @RequestParam(value = "status", required = false) final String status,
            @RequestParam(value = "preset_id", required = false) final Long presetId,
            @RequestParam(value = "has_feed", required = false) Boolean hasFeed)
    {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);

        if (limit < 0 || offset < 0) {
            throw new BadRequestException();
        }
        VideoPreset videoPreset = null;
        if (presetId != null) {
            try {
                videoPreset = videoPresetsService.getPreset(presetId);
            } catch (Exception e) {
                throw new BadRequestException("unknown preset_id");
            }
        }
        FileStatus fileStatus = null;
        if (status != null) {
            try {
                fileStatus = FileStatus.fromString(status);
            } catch (Exception e) {
                throw new BadRequestException("unknown status");
            }
        }
        List<String> ratios = null;
        if (aspectRatioWidth != null && aspectRatioHeight != null) {
            ratios = ImmutableList.of(aspectRatioWidth + ":" + aspectRatioHeight);
        }
        return videoConstructorFilesService.find(clientId, name, "desc".equalsIgnoreCase(sortOrder), offset, limit,
                ratios, durationFrom, durationTo, hasAudio, fileStatus, hasFeed, videoPreset);
    }

    @GetMapping(value = "{id}")
    public VideoConstructorFile getById(@PathVariable("id") final String id,
                                        @RequestParam("client_id") final long clientId)
    {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);
        return Optional.ofNullable(videoConstructorFilesService.getById(id, clientId)).orElseThrow(NotFoundException::new);
    }

    @AuthorizeBy({AuthorizeBy.AuthType.TRUSTED_QUERY_STRING})
    @GetMapping
    public VideoConstructorFile getByIdInQuery(@RequestParam("id") final String id,
                                               @RequestParam("client_id") final long clientId)
    {
        return Optional.ofNullable(videoConstructorFilesService.getById(id, clientId)).orElseThrow(NotFoundException::new);
    }

    @DeleteMapping(value = "{id}")
    public ResponseEntity<Void> delete(@PathVariable("id") final String id,
            @RequestParam("client_id") final long clientId)
    {
        authService.requirePermission(Privileges.Permission.CREATIVE_CREATE);
        videoConstructorFilesService.delete(id, clientId);
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }

    @AuthorizeBy({SANDBOX_SECRET})
    @PostMapping(path = "{id}/sandbox-hook")
    public ResponseEntity<VideoConstructorFilesController.EventResponse> registerSandboxEvent(
            @PathVariable("id") String fileId,
            @RequestBody SandBoxService.SandboxVideoConstructorTaskOutput request
    ) {
        try {
            videoConstructorFilesService.updateConvertingFile(fileId, request);
        } catch (Exception e) {
            throw new InternalServerError(e);
        }
        return ResponseEntity.ok(new VideoConstructorFilesController.EventResponse().setOk(true));
    }

    @AuthorizeBy({SANDBOX_SECRET})
    @PostMapping(path = "sandbox-hook-update")
    public ResponseEntity<VideoConstructorFilesController.EventResponse> registerSandboxEventUpdate(
            @RequestBody List<SandBoxService.SandboxVideoConstructorTaskOutput> request
    ) {
        try {
            videoConstructorFilesService.updateFiles(request);
        } catch (Exception e) {
            throw new InternalServerError(e);
        }
        return ResponseEntity.ok(new VideoConstructorFilesController.EventResponse().setOk(true));
    }

    public static class EventResponse {
        @JsonProperty("OK")
        Boolean ok;

        public Boolean getOk() {
            return ok;
        }

        public VideoConstructorFilesController.EventResponse setOk(Boolean ok) {
            this.ok = ok;
            return this;
        }
    }

    public static class CreateVideoConstructorFileRequest {
        @JsonProperty("file")
        private VideoConstructorFile videoConstructorFile;

        @JsonProperty("crop_params")
        private List<VideoConstructorFeedCropParams> cropParams;

        public VideoConstructorFile getVideoConstructorFile() {
            return videoConstructorFile;
        }

        public List<VideoConstructorFeedCropParams> getCropParams() {
            return cropParams;
        }
    }

}
