package ru.yandex.canvas.service;

import java.io.IOException;
import java.net.URI;
import java.sql.Date;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import org.springframework.data.domain.Sort;

import ru.yandex.canvas.model.stillage.StillageFileInfo;
import ru.yandex.canvas.model.video.VideoFiles;
import ru.yandex.canvas.model.video.files.FileStatus;
import ru.yandex.canvas.model.video.files.FileType;
import ru.yandex.canvas.model.video.files.PackShot;
import ru.yandex.canvas.repository.ItemsWithTotal;
import ru.yandex.canvas.repository.video.QueryBuilderBase;
import ru.yandex.canvas.repository.video.VideoFilesRepository;
import ru.yandex.canvas.service.video.PackshotServiceInterface;
import ru.yandex.canvas.service.video.PackshotUploadingService;
import ru.yandex.canvas.service.video.Ratio;
import ru.yandex.canvas.service.video.VideoCreativeType;
import ru.yandex.canvas.service.video.VideoGeometryService;
import ru.yandex.canvas.service.video.VideoLimitsService;
import ru.yandex.canvas.service.video.VideoPresetsService;
import ru.yandex.canvas.service.video.presets.VideoPreset;

import static ru.yandex.canvas.VideoConstants.CANVAS_VIDEO_ANY_SIZE_ALLOWED_FEATURE;
import static ru.yandex.direct.utils.CommonUtils.ifNotNull;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;

public class PackshotService implements PackshotServiceInterface {
    private final VideoFilesRepository videoFilesRepository;
    private final SessionParams sessionParams;
    private final PackshotUploadingService fileUploadService;
    private final StillageService stillageService;
    private final VideoLimitsService videoLimitsService;
    private final DirectService directService;
    private final VideoPresetsService videoPresetsService;
    private final VideoGeometryService videoGeometryService;


    public PackshotService(VideoFilesRepository videoFilesRepository,
                           SessionParams sessionParams, PackshotUploadingService fileUploadService,
                           StillageService stillageService, VideoLimitsService videoLimitsService,
                           DirectService directService, VideoPresetsService videoPresetsService,
                           VideoGeometryService videoGeometryService) {
        this.videoFilesRepository = videoFilesRepository;
        this.sessionParams = sessionParams;
        this.fileUploadService = fileUploadService;
        this.stillageService = stillageService;
        this.videoLimitsService = videoLimitsService;
        this.directService = directService;
        this.videoPresetsService = videoPresetsService;
        this.videoGeometryService = videoGeometryService;
    }

    public PackShot lookupPackshot(String id, Long clientId) {
        if (id == null) {
            return null;
        }

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

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

        if (file == null) {
            return null;
        }

        return new PackShot(file);
    }

    @Override
    public String markFileUsed(PackShot file, Long clientId) {

        if (file == null || file.getId() == null) {
            return null;
        }

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

        queryBuilder.withClientId(clientId)
                .withId(file.getId());

        videoFilesRepository.markFileUsed(queryBuilder);
        return file.getId();
    }

    @Override
    public PackShot upload(URI uri, String filename, Long clientId,
                           VideoCreativeType videoCreativeType, Long presetId) throws IOException {
        StillageFileInfo info = stillageService.uploadFile(filename, uri.toURL());
        return upload(info, filename, clientId, videoCreativeType, presetId);
    }

    @Override
    public PackShot upload(byte[] content, String filename, Long clientId,
                           VideoCreativeType videoCreativeType, Long presetId) {
        StillageFileInfo info = stillageService.uploadFile(filename, content);
        return upload(info, filename, clientId, videoCreativeType, presetId);
    }

    public PackShot upload(StillageFileInfo info, String filename, Long clientId,
                           VideoCreativeType videoCreativeType, Long presetId) {
        VideoFiles record = makeVideoFilesRecordForUpload(clientId, filename);

        return new PackShot(fileUploadService.upload(record, info, presetId));
    }

    public StillageFileInfo processFileInfo(StillageFileInfo info, String filename,
                                            VideoCreativeType videoCreativeType) {
        return info;
    }

    private VideoFiles makeVideoFilesRecordForUpload(Long clientId, String filename) {
        VideoFiles record = new VideoFiles();
        record.setStatus(FileStatus.READY);
        record.setClientId(clientId);
        record.setName(filename);
        record.setType(getImageFileType());
        record.setArchive(false);
        record.setDate(Date.from(Instant.now()));
        record.setCreativeType(null);

        return record;
    }


    @Override
    public boolean delete(PackShot file, Long clientId) {
        return videoFilesRepository.deleteFile(file.getId(), clientId, getImageFileType());
    }

    @Override
    public PackShot makeFromDb(VideoFiles record) {
        if (record.getCreativeType() != null && record.getCreativeType() != sessionParams.getCreativeType()) {
            throw new IllegalArgumentException(
                    "Could not create object from " + record.getCreativeType() + "in " + sessionParams.getCreativeType()
                            + " session");
        }

        return new PackShot(record);
    }

    @Override
    public ItemsWithTotal<PackShot> getUserFiles(Long clientId, String nameSubstring, Sort.Direction direction,
                                                 int limit,
                                                 int offset, VideoCreativeType videoCreativeType, Long presetId,
                                                 @Nullable Boolean showFeeds) {
        VideoFilesRepository.QueryBuilder queryBuilder = new VideoFilesRepository.QueryBuilder();
        queryBuilder.withType(getImageFileType())
                .withClientId(clientId)
                .withArchive(false);

        if (nameSubstring != null) {
            queryBuilder.withNameRegexp(".*" + Pattern.quote(nameSubstring) + ".+", "i");
        }

        List<QueryBuilderBase<?>> additionalCriteries = buildImageSearchQuery(videoCreativeType,
                directService.getFeatures(clientId, null), videoPresetsService.getPresetSafe(presetId));

        queryBuilder.and(additionalCriteries);

        return new ItemsWithTotal<>(
                videoFilesRepository.findByQuery(queryBuilder, direction, limit, offset).stream()
                        .map(this::makeFromDb).collect(Collectors.toList()),
                videoFilesRepository.count(queryBuilder)
        );
    }

    protected List<QueryBuilderBase<?>> buildImageSearchQuery(VideoCreativeType videoCreativeType,
                                                              Set<String> features,
                                                              VideoPreset preset) {
        //Нужно знать фичи клиента и пресет
        boolean anyRatios = features.contains(CANVAS_VIDEO_ANY_SIZE_ALLOWED_FEATURE);
        List<QueryBuilderBase<?>> queryBuilders = new ArrayList<>();

        if (!anyRatios) {
            if (preset != null && videoGeometryService.hasAllowedRatiosInterval(preset.getDescription().getGeometry(),
                    features, videoCreativeType)) {

                var ratioInterval =
                        videoGeometryService.getRatiosByPreset(preset.getDescription().getGeometry(), features,
                                videoCreativeType);

                var ratioQueryFilter = new VideoFilesRepository.QueryBuilder()
                        .withRatiosBetweenExact(ratioInterval.getFrom(), ratioInterval.getTo());

                queryBuilders.add(ratioQueryFilter);
            } else {
                queryBuilders.add(new VideoFilesRepository.QueryBuilder() //Or  always looking for 16:9 (?)
                        .withRatios(mapList(videoGeometryService.getAllowedRatios(videoCreativeType,
                                ifNotNull(preset, VideoPreset::getId)), e -> ifNotNull(e, Ratio::toString))));
            }

            if (videoCreativeType == VideoCreativeType.CPM_INDOOR) {
                VideoLimitsInterface videoLimits = videoLimitsService.getLimits(videoCreativeType, features,
                        ifNotNull(preset, VideoPreset::getId));
                queryBuilders.add(new VideoFilesRepository.QueryBuilder().withHeightGreaterThan(
                        videoLimits.getVideoHeightMin().longValue()));
            }
        }

        return queryBuilders;
    }

    private FileType getImageFileType() {
        return sessionParams != null && sessionParams.isPresent() && sessionParams.getSessionType() == SessionParams.SessionTag.CPM_AUDIO ?
                FileType.AUDIO_IMAGE : FileType.IMAGE;
    }

}
