package com.atlassian.stash.internal.web.admin;

import com.atlassian.bitbucket.NoSuchEntityException;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.stash.internal.backup.Backup;
import com.atlassian.stash.internal.backup.BackupService;
import com.atlassian.stash.internal.maintenance.MaintenanceLock;
import com.atlassian.stash.internal.maintenance.MaintenanceService;
import com.atlassian.stash.internal.maintenance.UnsupportedMaintenanceException;
import com.atlassian.stash.internal.web.auth.IgnoresXsrf;
import com.atlassian.stash.internal.web.maintenance.RestErrorTexts;
import com.atlassian.stash.internal.web.maintenance.RestMaintenanceTaskHandle;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @since 2.1
 */
@Controller
@RequestMapping("/admin/backups")
public class BackupController {

    public static final String LATEST = "latest";

    private final BackupService backupService;
    private final MaintenanceService maintenanceService;

    @Autowired
    public BackupController(BackupService backupService, MaintenanceService maintenanceService) {
        this.backupService = backupService;
        this.maintenanceService = maintenanceService;
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "{name}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBackup(@PathVariable("name") String name) {
        Backup backup = backupService.findByName(name);
        if (backup != null) {
            backupService.delete(backup);
        }
    }

    @RequestMapping("{name}")
    public void downloadBackup(@PathVariable("name") String name, HttpServletResponse response) throws IOException {
        Backup backup;
        if (LATEST.equals(name)) {
            backup = backupService.getLatest();
        } else {
            backup = backupService.getByName(name);
        }

        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("application/zip");
        response.setHeader("Content-Length", Long.toString(backup.getSize()));
        response.setHeader("Content-Disposition", "attachment; filename=\"" + backup.getName() + "\"");

        try (InputStream backupStream = backup.getInput()) {
            ByteStreams.copy(backupStream, response.getOutputStream());
        }
        response.flushBuffer();
    }

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public RestBackupPage listBackups(@RequestParam(defaultValue = "0", required = false, value = "start") int start,
                                      @RequestParam(defaultValue = "25", required = false, value = "limit") int limit) {
        Page<Backup> page = backupService.findAll(PageUtils.newRequest(start, limit));

        return new RestBackupPage(page);
    }

    @IgnoresXsrf //Is not called from a normal form, so it will not have an XSRF token. Consuming JSON mime type protects against XSRF
    @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
                    produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.ACCEPTED)
    @ResponseBody
    public RestMaintenanceTaskHandle startBackup(@RequestParam(defaultValue = "false", required = false, value="external") boolean external) {
        if (external) {
            return new RestMaintenanceTaskHandle(backupService.externalBackup());
        }
        return new RestMaintenanceTaskHandle(backupService.backup());
    }

    @IgnoresXsrf //Is not called from a normal form, so it will not have an XSRF token. The 'token' param protects against XSRF
    @RequestMapping(value = "/progress/client", method = RequestMethod.POST,
                    produces = MediaType.APPLICATION_JSON_VALUE,
                    params = {"token", "percentage"})
    public void updateClientProgress(@RequestParam(value = "token") String token,
                                     @RequestParam(value = "percentage") int percentage,
                                     HttpServletResponse response) throws IOException {
        MaintenanceLock context = maintenanceService.getLock();
        if (context == null) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }

        if (!context.getUnlockToken().equals(token)) {
            response.setStatus(HttpStatus.CONFLICT.value());
            return;
        }

        try {
            backupService.updateClientProgress(percentage);
            response.setStatus(HttpStatus.ACCEPTED.value());
        } catch (NoSuchEntityException e) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
        }
    }

    @RequestMapping(method = RequestMethod.GET, params = "features", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<RestBackupFeature> getFeatures() {
        return Lists.transform(backupService.getFeatures(), RestBackupFeature::new);
    }

    @ResponseStatus(HttpStatus.CONFLICT)
    @ExceptionHandler(IllegalStateException.class)
    @ResponseBody
    public RestErrorTexts handleInProgress(Exception e) {
        return new RestErrorTexts(e.getLocalizedMessage());
    }

    @ResponseStatus(HttpStatus.CONFLICT)
    @ExceptionHandler(UnsupportedMaintenanceException.class)
    @ResponseBody
    public RestErrorTexts handleUnsupported(Exception e) {
        return new RestErrorTexts(e.getLocalizedMessage());
    }
}
