/*
 * Copyright 2016 the original author or authors.
 *
 * 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.
 */
package com.greglturnquist.learningspringboot.images;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Principal;
import java.util.UUID;

import io.micrometer.core.instrument.MeterRegistry;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;

/**
 * @author Greg Turnquist
 */
@Service
public class ImageService {

	private static String UPLOAD_ROOT = "upload-dir";

	private final ResourceLoader resourceLoader;
	private final ImageRepository imageRepository;

	// tag::metric-1[]
	private final MeterRegistry meterRegistry;

	public ImageService(ResourceLoader resourceLoader,
						ImageRepository imageRepository,
						MeterRegistry meterRegistry) {

		this.resourceLoader = resourceLoader;
		this.imageRepository = imageRepository;
		this.meterRegistry = meterRegistry;
	}
// end::metric-1[]

	public Flux<Image> findAllImages() {
		return imageRepository.findAll()
			.log("findAll");
	}


	public Mono<Resource> findOneImage(String filename) {
		return Mono.fromSupplier(() ->
			resourceLoader.getResource(
				"file:" + UPLOAD_ROOT + "/" + filename))
			.log("findOneImage");
	}

	// tag::metric-2[]
	public Mono<Void> createImage(Flux<FilePart> files,
								  Principal auth) {
		return files
			.log("createImage-files")
			.flatMap(file -> {
				Mono<Image> saveDatabaseImage = imageRepository.save(
					new Image(
						UUID.randomUUID().toString(),
						file.filename(),
						auth.getName()))
					.log("createImage-save");
				// end::metric-2[]

				Mono<Void> copyFile = Mono.just(Paths.get(UPLOAD_ROOT, file.filename()).toFile())
					.log("createImage-picktarget")
					.map(destFile -> {
						try {
							destFile.createNewFile();
							return destFile;
						} catch (IOException e) {
							throw new RuntimeException(e);
						}
					})
					.log("createImage-newfile")
					.flatMap(file::transferTo)
					.log("createImage-copy")
					.then(Mono.fromRunnable(() ->
						meterRegistry
							.summary("files.uploaded.bytes")
							.record(Paths.get(UPLOAD_ROOT, file.filename()).toFile().length())
					));

				return Mono.when(saveDatabaseImage, copyFile)
					.log("createImage-when");
			})
			.log("createImage-flatMap")
			.then()
			.log("createImage-done");
	}

	// tag::delete[]
	@PreAuthorize("hasRole('ADMIN') or " +
		"@imageRepository.findByName(#filename).owner " +
		"== authentication.name")
	public Mono<Void> deleteImage(String filename) {
		// end::delete[]
		Mono<Void> deleteDatabaseImage = imageRepository
			.findByName(filename)
			.log("deleteImage-find")
			.flatMap(imageRepository::delete)
			.log("deleteImage-record");

		Mono<Object> deleteFile = Mono.fromRunnable(() -> {
			try {
				Files.deleteIfExists(Paths.get(UPLOAD_ROOT, filename));
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		})
			.log("deleteImage-file");

		return Mono.when(deleteDatabaseImage, deleteFile)
			.log("deleteImage-when")
			.then()
			.log("deleteImage-done");
	}

	/**
	 * Pre-load some fake images
	 *
	 * @return Spring Boot {@link CommandLineRunner} automatically run after app context is loaded.
	 */
	@Bean
	CommandLineRunner setUp() throws IOException {
		return (args) -> {
			FileSystemUtils.deleteRecursively(new File(UPLOAD_ROOT));

			Files.createDirectory(Paths.get(UPLOAD_ROOT));

			FileCopyUtils.copy("Test file",
				new FileWriter(UPLOAD_ROOT +
					"/learning-spring-boot-cover.jpg"));

			FileCopyUtils.copy("Test file2",
				new FileWriter(UPLOAD_ROOT +
					"/learning-spring-boot-2nd-edition-cover.jpg"));

			FileCopyUtils.copy("Test file3",
				new FileWriter(UPLOAD_ROOT + "/bazinga.png"));
		};
	}

}