package ru.yandex.canvas.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.EnumUtils;
import org.asynchttpclient.AsyncHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestClientException;

import ru.yandex.canvas.exceptions.SandboxApiException;
import ru.yandex.canvas.model.video.VideoFiles;
import ru.yandex.direct.asynchttp.FetcherSettings;
import ru.yandex.direct.asynchttp.ParallelFetcherFactory;
import ru.yandex.direct.asynchttp.Result;
import ru.yandex.direct.http.smart.annotations.Json;
import ru.yandex.direct.http.smart.annotations.ResponseHandler;
import ru.yandex.direct.http.smart.core.Call;
import ru.yandex.direct.http.smart.core.Smart;
import ru.yandex.direct.http.smart.http.Body;
import ru.yandex.direct.http.smart.http.GET;
import ru.yandex.direct.http.smart.http.Headers;
import ru.yandex.direct.http.smart.http.POST;
import ru.yandex.direct.http.smart.http.PUT;
import ru.yandex.direct.http.smart.http.Path;

import static ru.yandex.direct.http.smart.error.ErrorUtils.checkResultForErrors;

public class SandBoxService {
    private static final Logger logger = LoggerFactory.getLogger(SandBoxService.class);
    private final Api api;

    public SandBoxService(String sandboxOAuthToken,
                          String sandboxBaseUrl,
                          AsyncHttpClient asyncHttpClient) {
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        this.api = createApi(sandboxBaseUrl, sandboxOAuthToken, fetcherFactory);
    }

    private Api createApi(String url, String oauthToken, ParallelFetcherFactory parallelFetcherFactory) {
        return Smart.builder()
                .withParallelFetcherFactory(parallelFetcherFactory)
                .withProfileName("sandbox_service")
                .withBaseUrl(url)
                .addHeaderConfigurator(headers -> headers.add(HttpHeaders.CONTENT_TYPE,
                        MediaType.APPLICATION_JSON_VALUE))
                .addHeaderConfigurator(headers -> headers.add(HttpHeaders.AUTHORIZATION,
                        "OAuth " + oauthToken))
                .build()
                .create(Api.class);
    }

    public interface Api {
        @POST("/task")
        @Json
        @Headers("Content-Type: application/json")
        @ResponseHandler(expectedCodes = {200, 201})
        Call<SandboxCreateResponse> createTask(@Body @Json SandboxCreateRequest createRequest);

        @GET("/task/{id}/output")
        @Json
        Call<List<TaskOutput>> getTaskOutputs(@Path("id") Long taskId);

        @GET("/task/{id}")
        @Json
        Call<TaskContent> getTask(@Path("id") Long taskId);

        @PUT("/batch/tasks/start")
        @Json
        @Headers("Content-Type: application/json")
        Call<List<SandboxStartTaskResponse>> startTask(@Body @Json StartTaskRequest startTaskRequest);

        @PUT("/batch/tasks/stop")
        @Json
        @Headers("Content-Type: application/json")
        Call<List<SandboxStartTaskResponse>> stopTask(@Body @Json StartTaskRequest startTaskRequest);
    }


    public enum SandboxTaskStatus {
        NOT_READY,
        ENQUEUED,
        ENQUEUING,
        EXECUTING,
        FINISHED,
        STOPPING,
        STOPPED,
        FAILURE,
        WAIT_CHILD,
        WAIT_DEPS,
        UNKNOWN,
        DELETED,
        EXCEPTION,
        RELEASED,
        DRAFT,
        TIMEOUT,
        WAIT_MUTEX,
        FINISHING,
        WAIT_RES,
        WAIT_TASK,
        SUSPENDING,
        EXPIRED,
        NOT_RELEASED,
        SUCCESS,
        ASSIGNED,
        TEMPORARY,
        NO_RES,
        RELEASING,
        WAIT_TIME,
        WAIT_OUT,
        RESUMING,
        PREPARING,
        PREPAIRING,
        SUSPENDED;

        public static final Set<SandboxTaskStatus> FINISH = ImmutableSet.of(FINISHED, FAILURE, DELETED, SUCCESS);
        public static final Set<SandboxTaskStatus> BREAK = ImmutableSet.of(UNKNOWN, STOPPED, EXCEPTION, TIMEOUT);
        public static final Set<SandboxTaskStatus> EXECUTE = ImmutableSet.of(EXECUTING, ASSIGNED, PREPARING, FINISHING, ENQUEUED);

        @JsonCreator
        public static SandboxTaskStatus fromString(String name) {
            SandboxTaskStatus parsed = EnumUtils.getEnum(SandboxTaskStatus.class, name);

            if (parsed == null) {
                return UNKNOWN;
            }

            return parsed;
        }

    }

    public void stopTask(Long convertionTaskId) {
        SandboxTaskStatus status = taskStatus(convertionTaskId);

        if (SandboxTaskStatus.BREAK.contains(status) || SandboxTaskStatus.FINISH.contains(status)) {
            logger.debug("task <{}> already in status <{}>", convertionTaskId, status);
            return;
        }

        StartTaskRequest startTaskRequest =
                new StartTaskRequest().setComment("description").setIds(Arrays.asList(convertionTaskId));

        Result<List<SandboxStartTaskResponse>> stopTaskResult = api.stopTask(startTaskRequest).execute();
        checkResultForErrors(stopTaskResult, SandboxApiException::new);

        List<SandboxStartTaskResponse> response = stopTaskResult.getSuccess();

        if (!response.get(0).getStatus().equals("SUCCESS")) {
            logger.warn("failed to stop sandbox task {}, message={}", convertionTaskId, response.get(0).getMessage());
        } else {
            logger.debug("stopped sandbox task {}", convertionTaskId);
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class TaskContent {
        SandboxTaskStatus status;

        public SandboxTaskStatus getStatus() {
            return status;
        }
    }

    public TaskContent getTask(Long taskId) {
        Result<TaskContent> taskContent = api.getTask(taskId).execute();
        checkResultForErrors(taskContent, SandboxApiException::new);
        return taskContent.getSuccess();
    }

    public SandboxTaskStatus taskStatus(Long conversionTaskId) {
        return getTask(conversionTaskId).getStatus();
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class TaskOutput {
        private String name;
        private Object value;

        public String getName() {
            return name;
        }

        public Object getValue() {
            return value;
        }
    }

    public SandboxConversionTaskOutput taskOutput(Long conversionTaskId) throws IOException {
        Result<List<TaskOutput>> getTaskOutputsResult = api.getTaskOutputs(conversionTaskId).execute();
        checkResultForErrors(getTaskOutputsResult, SandboxApiException::new);
        List<TaskOutput> outputs = getTaskOutputsResult.getSuccess();

        SandboxConversionTaskOutput output = new SandboxConversionTaskOutput();
        ObjectMapper objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        for (TaskOutput taskOutput : outputs) {
            if (taskOutput.getValue() == null) {
                continue;
            }

            if ("formats".equals(taskOutput.getName())) {
                TypeReference<List<VideoFiles.VideoFormat>> formatsTypeRef = new TypeReference<>() {};
                output.setFormats(objectMapper.readValue((String) taskOutput.getValue(), formatsTypeRef));
            } else if ("thumbnail_urls".equals(taskOutput.getName())) {
                TypeReference<List<String>> thumbsTypeRef = new TypeReference<>() {};
                output.setThumbnailUrls(objectMapper.readValue((String) taskOutput.getValue(), thumbsTypeRef));
            } else if ("stream_url".equals(taskOutput.getName())) {
                output.setStreamUrl((String) taskOutput.getValue());
            }
        }

        return output;
    }

    public static class StartTaskRequest {
        String comment;

        @JsonProperty("id")
        private List<Long> ids;

        public String getComment() {
            return comment;
        }

        public StartTaskRequest setComment(String comment) {
            this.comment = comment;
            return this;
        }

        public List<Long> getIds() {
            return ids;
        }

        public StartTaskRequest setIds(List<Long> ids) {
            this.ids = ids;
            return this;
        }
    }

    public static class SandboxStartTaskResponse {
        private String message;
        private Long id;
        private String status;

        public String getMessage() {
            return message;
        }

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

        public Long getId() {
            return id;
        }

        public SandboxStartTaskResponse setId(Long id) {
            this.id = id;
            return this;
        }

        public String getStatus() {
            return status;
        }

        public SandboxStartTaskResponse setStatus(String status) {
            this.status = status;
            return this;
        }
    }

    public void startTask(Long taskId) {
        StartTaskRequest startTaskRequest =
                new StartTaskRequest().setComment("description").setIds(Arrays.asList(taskId));

        logger.info("started task: {}", taskId);

        Result<List<SandboxStartTaskResponse>> startTaskResult = api.startTask(startTaskRequest).execute();
        checkResultForErrors(startTaskResult, SandboxApiException::new);

        List<SandboxStartTaskResponse> response = startTaskResult.getSuccess();

        if (response == null || response.isEmpty() || !response.get(0).getStatus().equals("SUCCESS")) {
            String msg;

            if (response != null && !response.isEmpty() && response.get(0).getMessage() != null) {
                msg = response.get(0).getMessage();
            } else {
                msg = "/batch/tasks/start[" + taskId + "] failed";
            }

            throw new RestClientException(msg);
        }

    }

    public Long createVideoConvertionTask(SandboxCreateRequest request) {
        Result<SandboxCreateResponse> createTaskResult = api.createTask(request).execute();
        checkResultForErrors(createTaskResult, SandboxApiException::new);

        Long taskId = createTaskResult.getSuccess().getId();
        logger.info("created task:{}, {}", taskId, request);
        return taskId;
    }

    public enum VideoTaskExtraFields {
        // иногда ffpmeg используется вместо ffmpeg - это опечатка которую уже не элементарно исправить,
        // т.к. она распространилась по другим репозиториям
        URL("url"),
        WEB_HOOK_URL("webhook_payload_url"),
        AUDIO_WEB_HOOK_URL("webhook_url"),
        VIDEO_CONSTRUCTOR_WEB_HOOK_URL("webhook_url"),
        VIDEO_CONSTRUCTOR_AUDIO_URL("audio_url"),
        VIDEO_CONSTRUCTOR_WIDTH("width"),
        VIDEO_CONSTRUCTOR_HEIGHT("height"),
        VIDEO_CONSTRUCTOR_FPS("fps"),
        VIDEO_CONSTRUCTOR_NAME("name"),
        VIDEO_CONSTRUCTOR_FEED_ROW_NUMBERS("row_numbers"),
        VIDEO_CONSTRUCTOR_TEMPLATE_ID("template_id"),
        VIDEO_CONSTRUCTOR_FEED_ARCHIVE_NAME("archive_name"),
        VIDEO_CONSTRUCTOR_FEED_AUDIO_URLS("audio_urls"),
        S3_KEY_PREFIX("s3_key_prefix"),
        S3_DIR("s3_dir"),
        S3_BUCKET("s3_bucket"),
        S3_TESTING("s3_testing"),
        FFMPEG_OUTPUT_FORMAT("ffmpeg_output_format"),
        FFMPEG_THUMBNAILS_SCENE("ffpmeg_thumbnails_scene"),
        FFMPEG_CAPTURE_STOP("ffpmeg_capture_stop"),
        FFMPEG_TWO_PASS("ffmpeg_two_pass"),
        FFMPEG_CAPTURE_START("ffpmeg_capture_start"),
        FFMPEG_SEGMENT_TIME("ffmpeg_segment_time"),
        FFMPEG_THUMBNAIL_T("ffpmeg_thumbnails_t"),
        FFMPEG_ENCODING_PRESET("ffpmeg_encoding_preset"),
        FFMPEG_FIRST_RESOLUTION("ffpmeg_first_resolution"),
        STRM_EMBED_READY("strm_embed_ready"),
        FFMPEG_FRAMERATE("ffmpeg_framerate"),
        FFMPEG_RESOLUTIONS("ffpmeg_resolutions"),
        FFMPEG_LOUDNORM("ffmpeg_loudnorm"),
        FFMPEG_SETPTS_PTS_STARTPTS("ffmpeg_setpts_pts_startpts"),
        KEEP_ASPECT_RATIO("keep_aspect_ratio"),
        FORCE_DURATION_CALCULATION("force_duration_calculation"),
        ID_TO_VIDEO_DATA("id_to_video_data");

        private String fieldName;

        VideoTaskExtraFields(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getFieldName() {
            return fieldName;
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class SandboxCreateResponse {
        Long id;

        public Long getId() {
            return id;
        }

        public SandboxCreateResponse setId(Long id) {
            this.id = id;
            return this;
        }
    }

    public static class SandboxCreateRequest {
        private String type;
        private String owner;

        @JsonProperty("requirements")
        @JsonInclude(JsonInclude.Include.NON_NULL)
        private SandboxRequirements requirements;

        @JsonProperty("custom_fields")
        private List<CustomField> customFields = new ArrayList<>();

        @JsonProperty("priority")
        private SandboxPriority sandboxPriority = new SandboxPriority();

        public SandboxCreateRequest addCustomField(VideoTaskExtraFields name, String value) {
            customFields.add(new CustomFieldWithStringValue(name.getFieldName(), value));
            return this;
        }

        public SandboxCreateRequest addCustomField(VideoTaskExtraFields name, Double value) {
            customFields.add(new CustomFieldWithNumericValue(name.getFieldName(), value));
            return this;
        }

        public SandboxCreateRequest addCustomField(VideoTaskExtraFields name, Boolean value) {
            customFields.add(new CustomFieldWithBooleanValue(name.getFieldName(), value));
            return this;
        }

        public SandboxCreateRequest setPriority(String priority, String subClass) {
            sandboxPriority.setPriorityClass(priority);
            sandboxPriority.setSubclass(subClass);
            return this;
        }

        @Override
        public String toString() {
            return "SandboxCreateRequest{" +
                    "type='" + type + '\'' +
                    ", owner='" + owner + '\'' +
                    ", customFields=" + customFields +
                    ", sandboxPriority=" + sandboxPriority +
                    '}';
        }

        public static abstract class CustomField<T extends CustomField, G> {
            private String name;
            private G value;

            public CustomField(String name, G value) {
                this.name = name;
                this.value = value;
            }

            public String getName() {
                return name;
            }

            public T setName(String name) {
                this.name = name;
                return (T) this;
            }

            public G getValue() {
                return value;
            }

            public T setValue(G value) {
                this.value = value;
                return (T) this;
            }

            @Override
            public String toString() {
                return "CustomField{" +
                        "name='" + name + '\'' +
                        ", value=" + value +
                        '}';
            }
        }

        public static class CustomFieldWithStringValue extends CustomField<CustomFieldWithStringValue, String> {
            public CustomFieldWithStringValue(String key, String value) {
                super(key, value);
            }
        }

        public static class CustomFieldWithNumericValue extends CustomField<CustomFieldWithNumericValue, Double> {
            public CustomFieldWithNumericValue(String key, Double value) {
                super(key, value);
            }
        }

        public static class CustomFieldWithBooleanValue extends CustomField<CustomFieldWithBooleanValue, Boolean> {
            public CustomFieldWithBooleanValue(String key, Boolean value) {
                super(key, value);
            }
        }

        public static class SandboxPriority {
            @JsonProperty("class")
            private String priorityClass;
            private String subclass;

            public String getPriorityClass() {
                return priorityClass;
            }

            public SandboxPriority setPriorityClass(String priorityClass) {
                this.priorityClass = priorityClass;
                return this;
            }

            public String getSubclass() {
                return subclass;
            }

            public SandboxPriority setSubclass(String subclass) {
                this.subclass = subclass;
                return this;
            }

            @Override
            public String toString() {
                return "SandboxPriority{" +
                        "priorityClass='" + priorityClass + '\'' +
                        ", subclass='" + subclass + '\'' +
                        '}';
            }
        }

        public static class SandboxRequirements {
            @JsonProperty("client_tags")
            private String clientTags;

            public SandboxRequirements setClientTags(String clientTags) {
                this.clientTags = clientTags;
                return this;
            }
        }


        public String getType() {
            return type;
        }

        public SandboxCreateRequest setType(String type) {
            this.type = type;
            return this;
        }

        public String getOwner() {
            return owner;
        }

        public SandboxCreateRequest setOwner(String owner) {
            this.owner = owner;
            return this;
        }

        public List<CustomField> getCustomFields() {
            return customFields;
        }

        public SandboxPriority getSandboxPriority() {
            return sandboxPriority;
        }

        public SandboxRequirements getRequirements() {
            return requirements;
        }

        public SandboxCreateRequest setRequirements(
                SandboxRequirements requirements) {
            this.requirements = requirements;
            return this;
        }
    }


    public static class FormatsDeserializer extends JsonEventDeserializer<VideoFiles.VideoFormat> {

        @Override
        public List<VideoFiles.VideoFormat> deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            TypeReference<List<VideoFiles.VideoFormat>> typeReference =
                    new TypeReference<List<VideoFiles.VideoFormat>>() {
                    };
            return deserialize(p, ctxt, VideoFiles.VideoFormat.class, typeReference);
        }
    }

    public static class ThumbnailDeserializer extends JsonEventDeserializer<String> {

        @Override
        public List<String> deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {
            };
            return deserialize(p, ctxt, String.class, typeReference);
        }
    }

    //# FIXME: sometimes formats come not as json-serialized string but as a subtree
    // And then we need custom deserializer
    public abstract static class JsonEventDeserializer<T> extends JsonDeserializer<List<T>> {

        @SuppressWarnings("unchecked")
        public List<T> deserialize(JsonParser p, DeserializationContext ctxt, Class<T> clazz,
                                   TypeReference typeReference)
                throws IOException, JsonProcessingException {

            JsonToken token = p.getCurrentToken();

            switch (token) {
                case VALUE_NULL:
                    return null;
                case VALUE_STRING:
                    return new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                            .readValue(p.getText(), typeReference);
                case START_ARRAY:
                    List<T> formats = new ArrayList<>();
                    JsonToken nextToken = p.nextToken();

                    do {
                        if (nextToken == JsonToken.START_OBJECT) {
                            formats.add(ctxt.readValue(p, clazz));
                        } else {
                            throw new IOException("Bad json");
                        }

                        nextToken = p.nextToken();
                    }
                    while (nextToken != JsonToken.END_ARRAY);

                    return formats;
                default:
                    throw new IOException("Unkexpected token " + token);
            }

        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class SandboxFfConversionTaskOutput {
        @JsonProperty("out_url")
        String outUrl;
        @JsonProperty("task_id")
        String taskId;

        public String getOutUrl() {
            return outUrl;
        }

        public SandboxFfConversionTaskOutput setOutUrl(String outUrl) {
            this.outUrl = outUrl;
            return this;
        }

        public String getTaskId() {
            return taskId;
        }

        public SandboxFfConversionTaskOutput setTaskId(String taskId) {
            this.taskId = taskId;
            return this;
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class SandboxVideoConstructorTaskOutput {
        @JsonProperty("id")
        String id;
        @JsonProperty("mp4_url")
        String mp4Url;
        @JsonProperty("mp4_stillage_id")
        String mp4StillageId;
        @JsonProperty("packshot_url")
        String packshotUrl;
        @JsonProperty("packshot_stillage_id")
        String packshotStillageId;
        @JsonProperty("preview_url")
        String previewUrl;
        @JsonProperty("preview_stillage_id")
        String previewStillageId;
        @JsonProperty("feed_archive_url")
        String feedArchiveUrl;
        @JsonProperty("feed_archive_urls")
        List<String> feedArchiveUrls;
        @JsonProperty("feed_mp4_urls")
        List<String> feedMp4Urls;
        @JsonProperty("task_id")
        String taskId;
        @JsonProperty("success")
        Boolean success;

        public String getId() {
            return id;
        }

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

        public String getMp4Url() {
            return mp4Url;
        }

        public SandboxVideoConstructorTaskOutput setMp4Url(String mp4Url) {
            this.mp4Url = mp4Url;
            return this;
        }

        public String getMp4StillageId() {
            return mp4StillageId;
        }

        public SandboxVideoConstructorTaskOutput setMp4StillageId(String mp4StillageId) {
            this.mp4StillageId = mp4StillageId;
            return this;
        }

        public String getPackshotUrl() {
            return packshotUrl;
        }

        public SandboxVideoConstructorTaskOutput setPackshotUrl(String packshotUrl) {
            this.packshotUrl = packshotUrl;
            return this;
        }

        public String getPackshotStillageId() {
            return packshotStillageId;
        }

        public SandboxVideoConstructorTaskOutput setPackshotStillageId(String packshotStillageId) {
            this.packshotStillageId = packshotStillageId;
            return this;
        }

        public String getPreviewUrl() {
            return previewUrl;
        }

        public SandboxVideoConstructorTaskOutput setPreviewUrl(String previewUrl) {
            this.previewUrl = previewUrl;
            return this;
        }

        public String getPreviewStillageId() {
            return previewStillageId;
        }

        public SandboxVideoConstructorTaskOutput setPreviewStillageId(String previewStillageId) {
            this.previewStillageId = previewStillageId;
            return this;
        }

        public String getFeedArchiveUrl() {
            return feedArchiveUrl;
        }

        public SandboxVideoConstructorTaskOutput setFeedArchiveUrl(String feedArchiveUrl) {
            this.feedArchiveUrl = feedArchiveUrl;
            return this;
        }

        public List<String> getFeedArchiveUrls() {
            return feedArchiveUrls;
        }

        public SandboxVideoConstructorTaskOutput setFeedArchiveUrls(List<String> feedArchiveUrls) {
            this.feedArchiveUrls = feedArchiveUrls;
            return this;
        }

        public List<String> getFeedMp4Urls() {
            return feedMp4Urls;
        }

        public SandboxVideoConstructorTaskOutput setFeedMp4Urls(List<String> feedMp4Urls) {
            this.feedMp4Urls = feedMp4Urls;
            return this;
        }

        public String getTaskId() {
            return taskId;
        }

        public SandboxVideoConstructorTaskOutput setTaskId(String taskId) {
            this.taskId = taskId;
            return this;
        }

        public Boolean getSuccess() {
            return success;
        }

        public void setSuccess(Boolean success) {
            this.success = success;
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class SandboxConversionTaskOutput {
        @JsonProperty("formats")
        @JsonDeserialize(using = FormatsDeserializer.class)
        List<VideoFiles.VideoFormat> formats;

        @JsonDeserialize(using = ThumbnailDeserializer.class)
        @JsonProperty("thumbnail_urls")
        List<String> thumbnailUrls;

        @JsonProperty("stream_url")
        String streamUrl;

        public List<VideoFiles.VideoFormat> getFormats() {
            return formats;
        }

        public SandboxConversionTaskOutput setFormats(List<VideoFiles.VideoFormat> formats) {
            this.formats = formats;
            return this;
        }

        public List<String> getThumbnailUrls() {
            return thumbnailUrls;
        }

        public SandboxConversionTaskOutput setThumbnailUrls(List<String> thumbnailUrls) {
            this.thumbnailUrls = thumbnailUrls;
            return this;
        }

        public String getStreamUrl() {
            return streamUrl;
        }

        public SandboxConversionTaskOutput setStreamUrl(String streamUrl) {
            this.streamUrl = streamUrl;
            return this;
        }
    }
}
