package eu.eudat.controllers;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.data.entities.FileUpload;
import eu.eudat.exceptions.security.UnauthorisedException;
import eu.eudat.logic.managers.DatasetProfileManager;
import eu.eudat.logic.security.claims.ClaimedAuthorities;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.documents.helpers.FileEnvelope;
import eu.eudat.logic.utilities.json.JsonSearcher;
import eu.eudat.models.HintedModelFactory;
import eu.eudat.models.data.datasetwizard.DatasetWizardModel;
import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.security.Principal;
import eu.eudat.types.ApiMessageCode;
import eu.eudat.types.Authorities;
import org.apache.poi.util.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.transaction.Transactional;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@RestController
@CrossOrigin
@RequestMapping(value = {"/api/file/"})
public class FileController {

    private DatasetProfileManager datasetProfileManager;
    private final Environment environment;
    private DatabaseRepository databaseRepository;

    @Autowired
    public FileController(DatasetProfileManager datasetProfileManager, Environment environment, ApiContext apiContext) {
        this.datasetProfileManager = datasetProfileManager;
        this.environment = environment;
        this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
    }

    @RequestMapping(method = RequestMethod.POST, value = {"/upload"})
    public ResponseEntity<ResponseItem<String>> upload(
            @RequestParam("file") MultipartFile file, @RequestParam("datasetProfileId") String datasetProfileId, @RequestParam("fieldId") String fieldId,
            @ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER}) Principal principal)
            throws IllegalAccessException, IOException {
        String uuid = UUID.randomUUID().toString();

        eu.eudat.models.data.admin.composite.DatasetProfile datasetprofile = this.datasetProfileManager.getDatasetProfile(datasetProfileId);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        String json = mapper.writeValueAsString(datasetprofile.getSections());;
        JsonNode propertiesJson = mapper.readTree(json);
        Set<JsonNode> fieldNodes = new HashSet<>();
        fieldNodes.addAll(JsonSearcher.findNodes(propertiesJson, "id", fieldId, false));

//        AtomicReference<String> exceptionMessage = null;
        AtomicBoolean acceptedFile = new AtomicBoolean(false);
        fieldNodes.forEach(node -> {
            JsonNode data = node.get("data");
            if (data != null && !data.toString().equals("\"\"") && !data.toString().equals("null")) {
                String stringValue = data.toString().replaceAll("=", ":");
                JSONObject dataObj = new JSONObject(stringValue);
                Map<String, Object> dataMap = ((JSONObject) dataObj).toMap();
                if(dataMap.get("maxFileSizeInMB") != null && !dataMap.get("maxFileSizeInMB").toString().equals("\"\"") && !dataMap.get("maxFileSizeInMB").toString().equals("null")) {
                    if (file.getSize() <= Integer.parseInt(dataMap.get("maxFileSizeInMB").toString())*1048576) {
                        acceptedFile.set(true);
                    }
//                    else {
//                        exceptionMessage.set("The file is too large. Max file upload is " + dataMap.get("maxFileSizeInMB").toString() + " MB.");
//                    }
                }

                if(acceptedFile.get() && data.get("types") != null && !data.get("types").toString().equals("\"\"") && !data.get("types").toString().equals("null")) {
                    acceptedFile.set(false);

                    JSONArray types = new JSONArray(data.get("types").toString());

                    types.iterator().forEachRemaining(element -> {
                        Map<String, Object> typesMap = ((JSONObject) element).toMap();
                        if(typesMap.get("value") != null && !typesMap.get("value").toString().equals("\"\"") && !typesMap.get("value").toString().equals("null")) {
                            if(file.getContentType().equals(typesMap.get("value").toString())) {
                                acceptedFile.set(true);
                            }
                        }
                    });
                }
//                if(!acceptedFile.get()) {
//                    exceptionMessage.set("The file type is not accepted.");
//                }
            }
        });

        if(!acceptedFile.get()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("The uploaded file is too large or has an unaccepted type"));
        }


        File convFile = new File(this.environment.getProperty("temp.temp") + uuid);
        convFile.createNewFile();
        FileOutputStream fos = new FileOutputStream(convFile);
        fos.write(file.getBytes());
        fos.close();


        return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().payload(uuid)
                .status(ApiMessageCode.SUCCESS_MESSAGE).message(""));
    }

    @RequestMapping(method = RequestMethod.POST, value = {"/delete-temp"})
    public ResponseEntity<ResponseItem<String>> upload(@RequestBody String filename) throws IllegalAccessException, IOException {
        File convFile = new File(this.environment.getProperty("temp.temp") + filename);
//        Boolean deleted = convFile.delete();
        Boolean deleted = Files.deleteIfExists(convFile.toPath());

        return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().payload(deleted.toString())
                .status(ApiMessageCode.SUCCESS_MESSAGE).message(""));
    }

    @Transactional
    @RequestMapping(method = RequestMethod.GET, value = {"{id}"}, produces = "application/json")
    public @ResponseBody
    ResponseEntity download(@PathVariable String id
        ,@ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal
    ) throws IOException {
        FileUpload fileUpload = databaseRepository.getFileUploadDao().find(UUID.fromString(id));
        if(fileUpload == null) {
            throw new NoSuchElementException("File with id "+id+" not found");
        }

        if(fileUpload.getEntityType().name().equals(FileUpload.EntityType.DATASET.name())) {
            eu.eudat.data.entities.Dataset datasetEntity = databaseRepository.getDatasetDao().find(fileUpload.getEntityId(), HintedModelFactory.getHint(DatasetWizardModel.class));
            if (datasetEntity == null) {
                throw new NoSuchElementException("No dataset with id " + fileUpload.getEntityId() + "  found. This dataset was related to the file with id " + id);
            }
            if (!datasetEntity.getDmp().isPublic() && datasetEntity.getDmp().getUsers()
                    .stream().filter(userInfo -> userInfo.getUser().getId() == principal.getId())
                    .collect(Collectors.toList()).size() == 0)
                throw new UnauthorisedException();
        }

        FileEnvelope fileEnvelope = new FileEnvelope();
        fileEnvelope.setFilename(fileUpload.getName());
        File exportFile = new File(this.environment.getProperty("file.storage") + id);
        fileEnvelope.setFile(exportFile);

        InputStream resource = new FileInputStream(fileEnvelope.getFile());
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentLength(fileEnvelope.getFile().length());
        responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        String fileName = fileEnvelope.getFilename().replace(" ", "_").replace(",", "_");
        responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName);
        responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
        responseHeaders.set("Cache-Control", "no-store");
        responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");

        byte[] content = IOUtils.toByteArray(resource);
        resource.close();
        return new ResponseEntity<>(content,
                responseHeaders,
                HttpStatus.OK);

    }

}
