package com.thinkbiganalytics.feedmgr.rest.controller;

/*-
 * #%L
 * thinkbig-feed-manager-controller
 * %%
 * Copyright (C) 2017 ThinkBig Analytics
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import com.fasterxml.jackson.core.type.TypeReference;
import com.thinkbiganalytics.feedmgr.rest.ImportComponent;
import com.thinkbiganalytics.feedmgr.rest.model.ImportComponentOption;
import com.thinkbiganalytics.feedmgr.rest.model.ImportFeedOptions;
import com.thinkbiganalytics.feedmgr.rest.model.ImportProperty;
import com.thinkbiganalytics.feedmgr.rest.model.ImportTemplateOptions;
import com.thinkbiganalytics.feedmgr.rest.model.UploadProgress;
import com.thinkbiganalytics.feedmgr.rest.model.UserFieldCollection;
import com.thinkbiganalytics.feedmgr.service.MetadataService;
import com.thinkbiganalytics.feedmgr.service.UploadProgressService;
import com.thinkbiganalytics.feedmgr.service.feed.ExportImportFeedService;
import com.thinkbiganalytics.feedmgr.service.template.ExportImportTemplateService;
import com.thinkbiganalytics.feedmgr.util.ImportUtil;
import com.thinkbiganalytics.json.ObjectMapperSerializer;
import com.thinkbiganalytics.rest.model.RestResponseStatus;

import org.apache.commons.lang.StringUtils;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;

/**
 * REST API for administrative functions.
 */
@Api(tags = "Feed Manager - Administration", produces = "application/json")
@Path(AdminController.BASE)
@SwaggerDefinition(tags = @Tag(name = "Feed Manager - Administration", description = "administrator operations"))
public class AdminController {

    public static final String BASE = "/v1/feedmgr/admin";
    public static final String IMPORT_TEMPLATE = "/import-template";
    public static final String IMPORT_FEED = "/import-feed";

    public static final String IMPORT_TEMPLATE_NEW = "/import-template2";
    public static final String IMPORT_FEED_NEW = "/import-feed2";

    @Inject
    ExportImportTemplateService exportImportTemplateService;

    @Inject
    ExportImportFeedService exportImportFeedService;

    @Inject
    UploadProgressService uploadProgressService;

    /**
     * Feed manager metadata service
     */
    @Inject
    MetadataService metadataService;

    @GET
    @Path("/export-template/{templateId}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Exports the template with the specified ID.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the template as an attachment."),
                      @ApiResponse(code = 400, message = "the templateId is invalid.", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "The template is not available.", response = RestResponseStatus.class)
                  })
    public Response exportTemplate(@NotNull @Size(min = 36, max = 36, message = "Invalid templateId size")
                                   @PathParam("templateId") String templateId) {
        ExportImportTemplateService.ExportTemplate zipFile = exportImportTemplateService.exportTemplate(templateId);
        return Response.ok(zipFile.getFile(), MediaType.APPLICATION_OCTET_STREAM)
            .header("Content-Disposition", "attachments; filename=\"" + zipFile.getFileName() + "\"") //optional
            .build();
    }

    @GET
    @Path("/export-feed/{feedId}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Exports the feed with the specified ID.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the feed as an attachment."),
                      @ApiResponse(code = 400, message = "The feedId is invalid.", response = RestResponseStatus.class),
                      @ApiResponse(code = 404, message = "The feed is not available.", response = RestResponseStatus.class)
                  })
    public Response exportFeed(@NotNull @Size(min = 36, max = 36, message = "Invalid feedId size")
                               @PathParam("feedId") String feedId) {
        try {
            ExportImportFeedService.ExportFeed zipFile = exportImportFeedService.exportFeed(feedId);
            return Response.ok(zipFile.getFile(), MediaType.APPLICATION_OCTET_STREAM)
                .header("Content-Disposition", "attachments; filename=\"" + zipFile.getFileName() + "\"") //optional
                .build();
        } catch (IOException e) {
            throw new RuntimeException("Unable to export Feed " + e.getMessage());
        }
    }

    @GET
    @Path("/upload-status/{key}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Gets thet status of a given upload/import.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the upload status")
                  })
    public Response uploadStatus(@NotNull @PathParam("key") String key) {
        UploadProgress uploadProgress = uploadProgressService.getUploadStatus(key);
        if (uploadProgress != null) {
            uploadProgress.checkAndIncrementPercentage();
            return Response.ok(uploadProgress).build();
        } else {
            return Response.ok(uploadProgress).build();
        }
    }


    /**
     * This is used for quick import via a script.  The UI uses the AdminControllerV2 class
     */
    @POST
    @Path(IMPORT_FEED)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Imports a feed zip file.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the feed metadata.", response = ExportImportFeedService.ImportFeed.class),
                      @ApiResponse(code = 500, message = "There was a problem importing the feed.", response = RestResponseStatus.class)
                  })
    public Response uploadFeed(@NotNull @FormDataParam("file") InputStream fileInputStream,
                               @NotNull @FormDataParam("file") FormDataContentDisposition fileMetaData,
                               @FormDataParam("overwrite") @DefaultValue("false") boolean overwrite,
                               @FormDataParam("overwriteFeedTemplate") @DefaultValue("false") boolean overwriteFeedTemplate,
                               @FormDataParam("categorySystemName") String categorySystemName,
                               @FormDataParam("importConnectingReusableFlow") @DefaultValue("NOT_SET") ImportTemplateOptions.IMPORT_CONNECTING_FLOW importConnectingFlow,
                               @FormDataParam("templateProperties") @DefaultValue("") String templateProperties,
                               @FormDataParam("feedProperties") @DefaultValue("") String feedProperties)
        throws Exception {
        ImportFeedOptions options = new ImportFeedOptions();
        String uploadKey = uploadProgressService.newUpload();
        options.setUploadKey(uploadKey);
        options.findImportComponentOption(ImportComponent.FEED_DATA).setOverwrite(overwrite);
        options.findImportComponentOption(ImportComponent.FEED_DATA).setUserAcknowledged(true);
        options.findImportComponentOption(ImportComponent.FEED_DATA).setShouldImport(true);

        options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setShouldImport(importConnectingFlow.equals(ImportTemplateOptions.IMPORT_CONNECTING_FLOW.YES));
        options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setUserAcknowledged(!importConnectingFlow.equals(ImportTemplateOptions.IMPORT_CONNECTING_FLOW.NOT_SET));
        options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setOverwrite(importConnectingFlow.equals(ImportTemplateOptions.IMPORT_CONNECTING_FLOW.YES) && overwriteFeedTemplate);

        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setOverwrite(overwriteFeedTemplate);
        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setUserAcknowledged(true);
        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setShouldImport(true);
        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setContinueIfExists(!overwriteFeedTemplate);

        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setOverwrite(overwriteFeedTemplate);
        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setUserAcknowledged(true);
        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setShouldImport(true);
        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setContinueIfExists(!overwriteFeedTemplate);

        options.setCategorySystemName(categorySystemName);

        if(StringUtils.isNotBlank(templateProperties)) {
            List<ImportProperty> properties = ObjectMapperSerializer.deserialize(templateProperties, new TypeReference<List<ImportProperty>>() {
            });
            options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setProperties(properties);
        }

        if(StringUtils.isNotBlank(feedProperties)) {
            List<ImportProperty> properties = ObjectMapperSerializer.deserialize(feedProperties, new TypeReference<List<ImportProperty>>() {
            });
            options.findImportComponentOption(ImportComponent.FEED_DATA).setProperties(properties);
        }

        byte[] content = ImportUtil.streamToByteArray(fileInputStream);
        ExportImportFeedService.ImportFeed importFeed = exportImportFeedService.importFeed(fileMetaData.getFileName(), content, options);

        return Response.ok(importFeed).build();
    }


    /**
     * This is used for quick import via scripts.  The UI uses the AdminControllerV2 class
     */
    @POST
    @Path(IMPORT_TEMPLATE)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Imports a template xml or zip file.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the template metadata.", response = ExportImportTemplateService.ImportTemplate.class),
                      @ApiResponse(code = 500, message = "There was a problem importing the template.", response = RestResponseStatus.class)
                  })
    public Response uploadTemplatex(@NotNull @FormDataParam("file") InputStream fileInputStream,
                                    @NotNull @FormDataParam("file") FormDataContentDisposition fileMetaData,
                                    @FormDataParam("overwrite") @DefaultValue("false") boolean overwrite,
                                    @FormDataParam("importConnectingReusableFlow") @DefaultValue("NOT_SET") ImportTemplateOptions.IMPORT_CONNECTING_FLOW importConnectingFlow,
                                    @FormDataParam("createReusableFlow") @DefaultValue("false") boolean createReusableFlow,
                                    @FormDataParam("templateProperties") @DefaultValue("") String templateProperties)
        throws Exception {

        ImportTemplateOptions options = new ImportTemplateOptions();
        String uploadKey = uploadProgressService.newUpload();
        options.setUploadKey(uploadKey);

        boolean isZip = fileMetaData.getFileName().endsWith("zip");

        if (isZip) {
            options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setShouldImport(importConnectingFlow.equals(ImportTemplateOptions.IMPORT_CONNECTING_FLOW.YES));
            options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setUserAcknowledged(!importConnectingFlow.equals(ImportTemplateOptions.IMPORT_CONNECTING_FLOW.NOT_SET));
            options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setOverwrite(importConnectingFlow.equals(ImportTemplateOptions.IMPORT_CONNECTING_FLOW.YES));

        } else {
            options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setShouldImport(createReusableFlow);
            options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setUserAcknowledged(true);
            options.findImportComponentOption(ImportComponent.REUSABLE_TEMPLATE).setOverwrite(createReusableFlow);
        }

        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setOverwrite(overwrite);
        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setUserAcknowledged(true);
        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setShouldImport(true);
        options.findImportComponentOption(ImportComponent.NIFI_TEMPLATE).setContinueIfExists(!overwrite);

        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setOverwrite(overwrite);
        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setUserAcknowledged(true);
        options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setShouldImport(true);
        if(StringUtils.isNotBlank(templateProperties)) {
            List<ImportProperty> properties = ObjectMapperSerializer.deserialize(templateProperties, new TypeReference<List<ImportProperty>>() {
            });
            options.findImportComponentOption(ImportComponent.TEMPLATE_DATA).setProperties(properties);
        }

        byte[] content = ImportUtil.streamToByteArray(fileInputStream);
        ExportImportTemplateService.ImportTemplate importTemplate = exportImportTemplateService.importTemplate(fileMetaData.getFileName(), content, options);

        return Response.ok(importTemplate).build();
    }


    /**
     * Gets the user-defined fields for all categories and feeds.
     *
     * @return the user-defined fields
     * @since 0.4.0
     */
    @GET
    @Path("user-fields")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Gets the user-defined fields for categories and feeds.")
    @ApiResponses(
        @ApiResponse(code = 200, message = "Returns the user-defined fields.", response = UserFieldCollection.class)
    )
    @Nonnull
    public Object getUserFields() {
        return metadataService.getUserFields();
    }

    /**
     * Sets the user-defined fields for all categories and feeds.
     *
     * @param userFields the new user-defined fields
     * @since 0.4.0
     */
    @POST
    @Path("user-fields")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Sets the user-defined fields for categories and feeds.")
    @ApiResponses(
        @ApiResponse(code = 204, message = "The user-defined fields have been updated.")
    )
    public void setUserFields(@Nonnull final UserFieldCollection userFields) {
        metadataService.setUserFields(userFields);
    }
}
