package ru.yandex.direct.web.entity.agencyofflinereport.controller;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import javax.servlet.http.HttpServletResponse;

import com.google.common.base.Suppliers;
import com.google.common.net.HttpHeaders;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.RequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import ru.yandex.direct.asynchttp.AsyncHttpExecuteException;
import ru.yandex.direct.asynchttp.FetcherSettings;
import ru.yandex.direct.asynchttp.ParallelFetcher;
import ru.yandex.direct.asynchttp.ParallelFetcherFactory;
import ru.yandex.direct.common.logging.EventType;
import ru.yandex.direct.common.logging.LoggingConfig;
import ru.yandex.direct.common.util.HttpUtil;
import ru.yandex.direct.core.entity.agencyofflinereport.service.AgencyOfflineReportParametersService;
import ru.yandex.direct.rbac.RbacRole;
import ru.yandex.direct.utils.io.RuntimeIoException;
import ru.yandex.direct.web.annotations.AllowedOperatorRoles;
import ru.yandex.direct.web.annotations.AllowedSubjectRoles;
import ru.yandex.direct.web.core.model.WebErrorResponse;
import ru.yandex.direct.web.core.model.WebResponse;
import ru.yandex.direct.web.core.security.captcha.DisableAutoCaptcha;
import ru.yandex.direct.web.core.security.csrf.CsrfCheck;
import ru.yandex.direct.web.entity.agencyofflinereport.ContentWithTypeAndDisposition;
import ru.yandex.direct.web.entity.agencyofflinereport.S3FileResponseParser;
import ru.yandex.direct.web.entity.agencyofflinereport.model.EnqueueReportRequestParams;
import ru.yandex.direct.web.entity.agencyofflinereport.model.GetAvailableReportsResponse;
import ru.yandex.direct.web.entity.agencyofflinereport.service.AgencyOfflineReportWebService;

import static ru.yandex.direct.web.core.security.authentication.DirectCookieAuthProvider.PARAMETER_ULOGIN;

@Controller
@RequestMapping(value = "/agency_offline_report")
@Api(tags = "agency_offline_report")
@AllowedOperatorRoles({RbacRole.SUPER, RbacRole.SUPERREADER, RbacRole.SUPPORT, RbacRole.MANAGER, RbacRole.AGENCY})
@AllowedSubjectRoles({RbacRole.AGENCY})
public class AgencyOfflineReportsController {
    private static final Logger logger = LoggerFactory.getLogger(AgencyOfflineReportsController.class);

    private final boolean useAccelRedirect;
    private final ParallelFetcherFactory parallelFetcherFactory;
    private final AgencyOfflineReportWebService agencyOfflineReportWebService;
    private final Supplier<String> cachedMaximumAvailableDate;

    @Autowired
    public AgencyOfflineReportsController(AsyncHttpClient asyncHttpClient,
                                          @Value("${offline-report-downloader.use-accel-redirect}") boolean useAccelRedirect,
                                          AgencyOfflineReportParametersService parametersService,
                                          AgencyOfflineReportWebService agencyOfflineReportWebService) {
        this.parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        this.useAccelRedirect = useAccelRedirect;
        this.agencyOfflineReportWebService = agencyOfflineReportWebService;

        cachedMaximumAvailableDate = Suppliers
                .memoizeWithExpiration(parametersService::getMaximumAvailableDateAsString, 15, TimeUnit.MINUTES);
    }

    @ApiOperation(value = "Получить список отчетов", httpMethod = "GET")
    @ApiResponses(
            {
                    @ApiResponse(code = 403, message = "Permission denied", response = WebErrorResponse.class),
                    @ApiResponse(code = 200, message = "Ok", response = GetAvailableReportsResponse.class)
            }
    )
    @RequestMapping(path = "/list",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public WebResponse getAvailableReports(
            @RequestParam(value = PARAMETER_ULOGIN, required = false) @SuppressWarnings("unused") String subjectLogin) {
        return agencyOfflineReportWebService.listReports();
    }

    @ApiOperation(value = "Заказать отчет", httpMethod = "POST")
    @RequestMapping(path = "/enqueue",
            method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public WebResponse enqueueReportRequest(@RequestBody EnqueueReportRequestParams params,
                                            @RequestParam(value = PARAMETER_ULOGIN, required = false) @SuppressWarnings("unused") String subjectLogin) {
        return agencyOfflineReportWebService.enqueueReport(params);
    }

    @ApiOperation(value = "Скачать отчет. Возвращает внутренний редирект на скачивание или файл с отчетом",
            httpMethod = "GET")
    @RequestMapping(path = "/download",
            method = RequestMethod.GET)
    public void downloadReport(@RequestParam("report_id") Long reportId, HttpServletResponse response,
                               @RequestParam(value = PARAMETER_ULOGIN, required = false) @SuppressWarnings("unused") String subjectLogin) {
        String reportUrl = agencyOfflineReportWebService.getReportDownloadUrl(reportId);
        if (reportUrl == null) {
            // нет прав / отчет не найден / отчет не готов
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        if (useAccelRedirect) {
            try {
                HttpUtil.setInternalRedirectToS3(response, new URL(reportUrl));
            } catch (MalformedURLException e) {
                logger.error("Failed to parse report-{} url: {}", reportId, reportUrl);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        } else {
            // режим для локальной отладки
            respondFileFromS3(response, reportUrl);
        }
    }

    @DisableAutoCaptcha
    @CsrfCheck(enabled = false)
    @AllowedSubjectRoles({RbacRole.SUPER, RbacRole.SUPERREADER, RbacRole.SUPPORT, RbacRole.MANAGER, RbacRole.AGENCY})
    @LoggingConfig(enabled = EventType.NONE)
    @ApiOperation(value = "Получить максимальную дату, доступную к заказу отчета", httpMethod = "GET")
    @RequestMapping(path = "/getMaximumAvailableDate",
            method = RequestMethod.GET,
            produces = MediaType.TEXT_PLAIN_VALUE)
    @ResponseBody
    // список ролей в AllowedSubjectRoles должен совпадать с AllowedOperatorRoles
    public String getMaximumAvailableDate() {
        return cachedMaximumAvailableDate.get();
    }

    private void respondFileFromS3(HttpServletResponse response, String downloadUrl) {
        try (ParallelFetcher<ContentWithTypeAndDisposition> fetcher = parallelFetcherFactory.getParallelFetcher()) {
            S3FileResponseParser parser = new S3FileResponseParser(0, new RequestBuilder().setUrl(downloadUrl).build());
            ContentWithTypeAndDisposition s3Content = fetcher.executeWithErrorsProcessing(parser).getSuccess();
            response.setContentType(s3Content.getContentType());
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, s3Content.getContentDisposition());
            FileCopyUtils.copy(s3Content.getResponseBody(), response.getOutputStream());
        } catch (AsyncHttpExecuteException e) {
            logger.error("Failed to download: {}", downloadUrl);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (IOException e) {
            logger.error(String.format("Failed to respond with content of %s", downloadUrl), e);
            throw new RuntimeIoException(e);
        }
    }
}
