package ru.yandex.canvas.controllers.video;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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.PutMapping;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import ru.yandex.canvas.configs.auth.AuthorizeBy;
import ru.yandex.canvas.controllers.OffsetTotalItemsResponse;
import ru.yandex.canvas.controllers.video.wrappers.AudioFileWrapper;
import ru.yandex.canvas.controllers.video.wrappers.FileWrapper;
import ru.yandex.canvas.controllers.video.wrappers.InBannerWrapper;
import ru.yandex.canvas.controllers.video.wrappers.PackshotFileWrapper;
import ru.yandex.canvas.controllers.video.wrappers.VideoFileWrapper;
import ru.yandex.canvas.exceptions.BadRequestException;
import ru.yandex.canvas.exceptions.ConflictException;
import ru.yandex.canvas.exceptions.InternalServerError;
import ru.yandex.canvas.exceptions.NotFoundException;
import ru.yandex.canvas.model.direct.Privileges;
import ru.yandex.canvas.model.validation.NotWhitespace;
import ru.yandex.canvas.model.video.VideoFiles;
import ru.yandex.canvas.model.video.files.AudioSource;
import ru.yandex.canvas.model.video.files.FileType;
import ru.yandex.canvas.model.video.files.InBannerVideo;
import ru.yandex.canvas.model.video.files.MediaDataSource;
import ru.yandex.canvas.model.video.files.Movie;
import ru.yandex.canvas.model.video.files.PackShot;
import ru.yandex.canvas.repository.ItemsWithTotal;
import ru.yandex.canvas.repository.video.VideoFilesRepository;
import ru.yandex.canvas.service.AuthService;
import ru.yandex.canvas.service.DirectService;
import ru.yandex.canvas.service.SessionParams;
import ru.yandex.canvas.service.TankerKeySet;
import ru.yandex.canvas.service.video.AudioService;
import ru.yandex.canvas.service.video.InBannerVideoFilesService;
import ru.yandex.canvas.service.video.MovieServiceInterface;
import ru.yandex.canvas.service.video.PackshotServiceInterface;
import ru.yandex.canvas.service.video.VideoCreativeType;
import ru.yandex.canvas.service.video.VideoSoundTrackService;
import ru.yandex.canvas.service.video.files.StockMoviesService;

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

@RestController
@RequestMapping(path = "/video/files")
@Validated
public class VideoFilesController {
    private final StockMoviesService stockMoviesService;
    private final AuthService authService;
    private final VideoFilesRepository videoFilesRepository;

    private final MovieServiceInterface movieService;
    private final PackshotServiceInterface packshotService;
    private final AudioService audioService;
    private final VideoSoundTrackService videoSoundTrackService;
    private final InBannerVideoFilesService inBannerVideoFilesService;
    private final SessionParams sessionParams;
    private final DirectService directService;
    private static final Logger logger = LoggerFactory.getLogger(VideoFilesController.class);

    public VideoFilesController(
            StockMoviesService stockMoviesService, AuthService authService,
            VideoFilesRepository videoFilesRepository, MovieServiceInterface movieService,
            PackshotServiceInterface packshotService, AudioService audioService,
            VideoSoundTrackService videoSoundTrackService, SessionParams sessionParams,
            InBannerVideoFilesService inBannerVideoFilesService,
            DirectService directService) {
        this.stockMoviesService = stockMoviesService;
        this.authService = authService;
        this.videoFilesRepository = videoFilesRepository;
        this.movieService = movieService;
        this.packshotService = packshotService;
        this.audioService = audioService;
        this.videoSoundTrackService = videoSoundTrackService;
        this.inBannerVideoFilesService = inBannerVideoFilesService;
        this.sessionParams = sessionParams;
        this.directService = directService;
    }


    @GetMapping(path = "/my")
    public ResponseEntity<OffsetTotalItemsResponse<FileWrapper>> getMyFiles(
            @RequestParam("client_id") Long clientId,
            @RequestParam(value = "limit", defaultValue = "50") @Valid @PositiveOrZero Integer limit,
            @RequestParam(value = "offset", defaultValue = "0") @Valid @PositiveOrZero Integer offset,
            @RequestParam("type") @Valid @Pattern(regexp = "^(?:video|audio|image|in_banner)$") String type,
            @RequestParam(value = "sort_order", defaultValue = "desc") @Valid @Pattern(regexp = "^(?:asc|desc)$") String sortOrder,
            @RequestParam(value = "name", required = false) String nameSubString,
            @RequestParam(value = "preset_id", required = false) Long presetId,
            @RequestParam(value = "show_feeds", required = false) Boolean showFeeds,
            HttpServletRequest httpRequest
    ) {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);
        List<FileWrapper> records;

        Sort.Direction direction;

        if (sortOrder.equals("asc")) {
            direction = Sort.Direction.ASC;
        } else {
            direction = Sort.Direction.DESC;
        }

        long total;
        VideoCreativeType videoCreativeType = sessionParams.getCreativeType();

        switch (type.toLowerCase()) {
            case "in_banner":
                ItemsWithTotal<InBannerVideo> inBanners =
                        inBannerVideoFilesService.getUserFiles(clientId, nameSubString, direction, limit, offset,
                                VideoCreativeType.IN_BANNER, presetId, showFeeds);
                total = inBanners.getTotal();
                records = inBanners.getItems().stream().map(InBannerWrapper::new).collect(Collectors.toList());
                break;
            case "video":
                ItemsWithTotal<Movie> items =
                        movieService.getUserFiles(clientId, nameSubString, direction, limit, offset,
                                videoCreativeType, presetId, showFeeds);
                total = items.getTotal();
                Set<String> features = directService.getFeatures(clientId, null);
                records = items.getItems().stream()
                        .map(item -> new VideoFileWrapper(item, videoCreativeType, features))
                        .collect(Collectors.toList());
                break;
            case "audio":
                ItemsWithTotal<AudioSource> audios = isCpmOrVcAudio(sessionParams) ?
                        audioService.getUserFiles(clientId, nameSubString, direction, videoCreativeType, showFeeds,
                                limit, offset) :
                        videoSoundTrackService.getUserFiles(clientId, nameSubString, direction, limit, offset,
                                videoCreativeType, presetId, showFeeds);
                total = audios.getTotal();
                records = audios.getItems().stream().map(AudioFileWrapper::new).collect(Collectors.toList());
                break;
            case "image":
                ItemsWithTotal<PackShot> packshots =
                        packshotService.getUserFiles(clientId, nameSubString, direction, limit, offset,
                                videoCreativeType, presetId, showFeeds);
                total = packshots.getTotal();
                records =
                        packshots.getItems().stream().map(PackshotFileWrapper::new).collect(Collectors.toList());

                break;
            default:
                throw new BadRequestException("Unknown type " + type);
        }

        return ResponseEntity.ok(new OffsetTotalItemsResponse<>(
                records,
                0,
                total
        ));
    }

    /**
     * Для видеокреативов при загрузке аудиодорожке можно использовать только стоковые аудио.
     * Для аудиокреативов и видеоконструктора - любые.
     *
     * @param sessionParams
     * @return
     */
    private boolean isCpmOrVcAudio(SessionParams sessionParams) {
        return sessionParams.sessionIs(SessionParams.SessionTag.CPM_AUDIO) ||
                sessionParams.sessionIs(SessionParams.SessionTag.VIDEO_CONSTRUCTOR);
    }

    private FileType guessStockFileType(String id) {
        if (stockMoviesService.getAudio(id) != null) {
            return FileType.AUDIO;
        } else if (stockMoviesService.getVideo(id) != null) {
            return FileType.VIDEO;
        } else {
            return null;
        }
    }

    private FileWrapper makeFileWrapperFromStock(String fileId, FileType type, Long clientId) {
        switch (type) {
            case VIDEO:
                VideoCreativeType videoCreativeType = sessionParams.getCreativeType();
                Set<String> features = directService.getFeatures(clientId, null);

                return new VideoFileWrapper(stockMoviesService.getFileByIds(fileId, null), videoCreativeType, features);
            case AUDIO:
                return new AudioFileWrapper(stockMoviesService.getAudio(fileId));
            default:
                logger.info("Unknown file type: {} with id {} ", type, fileId);

                throw new InternalServerError("Unknown stock file type " + type);
        }
    }

    private FileWrapper makeFileWrapperFromDB(VideoFiles file, Long clientId) {

        switch (file.getType()) {
            case VIDEO:
                Movie movie = movieService.makeFromDb(file);

                if (movie == null) {
                    throw new NotFoundException();
                }

                VideoCreativeType videoCreativeType = sessionParams.getCreativeType();
                Set<String> features = directService.getFeatures(clientId, null);

                return new VideoFileWrapper(movie, videoCreativeType, features);

            case AUDIO:
                AudioSource audioSource = videoSoundTrackService.makeFromDb(file);
                return new AudioFileWrapper(audioSource);

            case IMAGE:
            case AUDIO_IMAGE:
                PackShot packShot = packshotService.makeFromDb(file);
                return new PackshotFileWrapper(packShot);

            case IN_BANNER:
                return new InBannerWrapper(inBannerVideoFilesService.makeFromDb(file));

            default:
                throw new BadRequestException("Unknown file type " + file.getType());
        }
    }

    @GetMapping(path = "/{id:.+}")
    public @ResponseBody
    ResponseEntity<FileWrapper> getFile(@PathVariable("id") String fileId,
                                        @RequestParam("client_id") Long clientId) {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);
        return getVideoFileByIdAndClientId(fileId, clientId);
    }

    /**
     * Ручка возвращает информацию о видео-файле по айдишнику с авторизацией по мастер-токену
     */
    @AuthorizeBy({DIRECT_TOKEN})
    @GetMapping(path = "/internal/{id:.+}")
    public @ResponseBody
    ResponseEntity<FileWrapper> internalGetFile(@PathVariable("id") String fileId,
                                                @RequestParam("client_id") Long clientId) {
        return getVideoFileByIdAndClientId(fileId, clientId);
    }

    private ResponseEntity<FileWrapper> getVideoFileByIdAndClientId(String fileId, Long clientId) {
        FileWrapper wrapper;
        FileType type = guessStockFileType(fileId);

        if (type != null) {
            wrapper = makeFileWrapperFromStock(fileId, type, clientId);
        } else {
            //проверить аудио в другой таблице
            AudioSource audioSource = audioService.lookupAudio(fileId, clientId);
            if (audioSource != null) {
                return ResponseEntity.ok(new AudioFileWrapper(audioSource));
            }

            VideoFilesRepository.QueryBuilder queryBuilder = new VideoFilesRepository.QueryBuilder()
                    .withClientId(clientId);

            VideoFiles file = videoFilesRepository.findByIdAndQuery(fileId, queryBuilder);

            if (file == null) {
                file = inBannerVideoFilesService.getFileByIdForCreativeType(fileId, queryBuilder);
                if (file == null) {
                    throw new NotFoundException();
                }
            }

            wrapper = makeFileWrapperFromDB(file, clientId);
        }

        return ResponseEntity.ok(wrapper);
    }

    @PostMapping(path = "/from-stock/{id}/used")
    public ResponseEntity<FileWrapper> useStockFile(
            @PathVariable("id") String stockFileId,
            @RequestParam("client_id") Long clientId
    ) {
        authService.requirePermission(Privileges.Permission.CREATIVE_CREATE);

        MediaDataSource file = stockMoviesService.getVideo(stockFileId);

        if (file == null) {
            file = stockMoviesService.getAudio(stockFileId);
        }

        if (file == null) {
            throw new NotFoundException();
        }

        String id = videoFilesRepository.markFileUsed(file, clientId);

        VideoFilesRepository.QueryBuilder queryBuilder = new VideoFilesRepository.QueryBuilder()
                .withClientId(clientId);

        VideoFiles mark = videoFilesRepository.findByIdAndQuery(id, queryBuilder);

        if (mark == null) {
            throw new InternalServerError();
        }

        return ResponseEntity.ok(makeFileWrapperFromDB(mark, clientId));
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class UpdateFileRequest {
        @JsonProperty("name")
        @Valid
        @NotEmpty
        @NotWhitespace
        @Size(min = 1, max = 1024)
        String name;
    }

    @PutMapping(path = "/{id}")
    public ResponseEntity<FileWrapper> updateFile(
            @PathVariable("id") String fileId,
            @RequestParam("client_id") Long clientId,
            @RequestBody @Valid UpdateFileRequest updateFileRequest
    ) {
        authService.requirePermission(Privileges.Permission.CREATIVE_CREATE);

        VideoFilesRepository.QueryBuilder queryBuilder = new VideoFilesRepository.QueryBuilder()
                .withId(fileId)
                .withClientId(clientId)
                .withArchive(false);

        VideoFilesRepository.UpdateBuilder updateBuilder = new VideoFilesRepository.UpdateBuilder()
                .withName(updateFileRequest.name);

        long updated;

        try {
            updated = videoFilesRepository.update(queryBuilder, updateBuilder);
        } catch (org.springframework.dao.DuplicateKeyException e) {
            throw new ConflictException(TankerKeySet.VIDEO.key("duplicate-name-error"));
        }

        if (updated == 0) {
            throw new NotFoundException(); //TODO message
        }

        VideoFiles renamed = videoFilesRepository
                .findByIdAndQuery(fileId, new VideoFilesRepository.QueryBuilder().withClientId(clientId));

        if (renamed == null) {
            throw new InternalServerError();
        }

        return ResponseEntity.ok(makeFileWrapperFromDB(renamed, clientId));
    }

    @DeleteMapping(path = "/{id}")
    public ResponseEntity deleteFile(
            @PathVariable("id") String fileId,
            @RequestParam("client_id") Long clientId
    ) {
        authService.requirePermission(Privileges.Permission.CREATIVE_CREATE);
        //fileId сквозной для разных таблиц https://docs.mongodb.com/v4.0/reference/method/ObjectId/
        if (audioService.delete(fileId, clientId)) {
            return ResponseEntity.status(204).build();
        }

        VideoFilesRepository.QueryBuilder queryBuilder = new VideoFilesRepository.QueryBuilder()
                .withArchive(false)
                .withClientId(clientId);

        VideoFiles file = videoFilesRepository.findByIdAndQuery(fileId, queryBuilder);

        if (file == null) {
            throw new NotFoundException();
        }

        switch (file.getType()) {
            case VIDEO:
                movieService.delete(movieService.makeFromDb(file), clientId);
                break;
            case AUDIO:
                videoSoundTrackService.delete(videoSoundTrackService.makeFromDb(file), clientId);
                break;
            case IN_BANNER:
                inBannerVideoFilesService.delete(inBannerVideoFilesService.makeFromDb(file), clientId);
                break;
            case IMAGE:
                packshotService.delete(packshotService.makeFromDb(file), clientId);
                break;
            default:
                throw new BadRequestException("Unknown file type " + file.getType());
        }

        return ResponseEntity.status(204).build();
    }

}
