package app.mealsmadeeasy.api.image;

import app.mealsmadeeasy.api.image.body.ImageUpdateBody;
import app.mealsmadeeasy.api.image.converter.ImageToViewConverter;
import app.mealsmadeeasy.api.image.converter.ImageUpdateBodyToSpecConverter;
import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.view.ImageView;
import app.mealsmadeeasy.api.sliceview.SliceViewService;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserService;
import app.mealsmadeeasy.api.util.AccessDeniedView;
import app.mealsmadeeasy.api.util.ResourceExistsView;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.InputStreamResource;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/images")
@RequiredArgsConstructor
public class ImageController {

    private final ImageService imageService;
    private final UserService userService;
    private final ImageToViewConverter imageToViewConverter;
    private final ImageUpdateBodyToSpecConverter imageUpdateBodyToSpecConverter;
    private final SliceViewService sliceViewService;

    @ExceptionHandler
    public ResponseEntity<AccessDeniedView> onAccessDenied(AccessDeniedException e) {
        if (e instanceof AuthorizationDeniedException) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(new AccessDeniedView(HttpStatus.FORBIDDEN.value(), e.getMessage()));
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(new AccessDeniedView(HttpStatus.UNAUTHORIZED.value(), e.getMessage()));
        }
    }

    @GetMapping
    public ResponseEntity<Object> getOwnedImages(
            @AuthenticationPrincipal User principal,
            @RequestParam(name = "count", defaultValue = "false") boolean doCount,
            Pageable pageable
    ) {
        if (doCount) {
            final int count = this.imageService.countOwnedImages(principal);
            return ResponseEntity.ok(Map.of("count", count));
        } else {
            final Slice<Image> images = this.imageService.getOwnedImages(principal, pageable);
            final Slice<ImageView> imageViews = images.map(image ->
                    this.imageToViewConverter.convert(image, principal, false)
            );
            return ResponseEntity.ok(this.sliceViewService.getSliceView(imageViews));
        }
    }

    @GetMapping("/{username}/{filename}")
    public ResponseEntity<InputStreamResource> getImage(
            @AuthenticationPrincipal User principal,
            @PathVariable String username,
            @PathVariable String filename
    ) throws IOException {
        final User owner = this.userService.getUser(username);
        final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
        final InputStream imageInputStream = this.imageService.getImageContent(image, principal);
        return ResponseEntity.status(200)
                .contentType(MediaType.parseMediaType(image.getMimeType()))
                .body(new InputStreamResource(imageInputStream));
    }

    @GetMapping("/{username}/{filename}/view")
    public ResponseEntity<ImageView> getImageView(
            @AuthenticationPrincipal User principal,
            @PathVariable String username,
            @PathVariable String filename
    ) {
        final User owner = this.userService.getUser(username);
        final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
        return ResponseEntity.ok(
                this.imageToViewConverter.convert(image, principal, false)
        );
    }

    @PostMapping
    public ResponseEntity<ImageView> uploadImage(
            @RequestParam MultipartFile image,
            @RequestParam String filename,
            @RequestParam(required = false) String alt,
            @RequestParam(required = false) String caption,
            @RequestParam(required = false) Boolean isPublic,
            @RequestParam(required = false) Set<String> viewers,
            @AuthenticationPrincipal User principal
    ) throws IOException, ImageException {
        final var specBuilder = ImageCreateSpec.builder()
                .alt(alt)
                .caption(caption)
                .isPublic(isPublic);

        if (viewers != null) {
            specBuilder.viewersToAdd(viewers.stream().map(this.userService::getUser).collect(Collectors.toSet()));
        }
        final Image saved = this.imageService.create(
                principal,
                filename,
                image.getInputStream(),
                image.getSize(),
                specBuilder.build()
        );
        return ResponseEntity.status(201).body(this.imageToViewConverter.convert(saved, principal, true));
    }

    @PutMapping("/{username}/{filename}")
    public ResponseEntity<ImageView> updateInfo(
            @AuthenticationPrincipal User principal,
            @PathVariable String username,
            @PathVariable String filename,
            @RequestBody ImageUpdateBody body
    ) {
        final User owner = this.userService.getUser(username);
        final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
        final Image updated = this.imageService.update(
                image,
                principal,
                this.imageUpdateBodyToSpecConverter.convert(body)
        );
        return ResponseEntity.ok(this.imageToViewConverter.convert(updated, principal, true));
    }

    @GetMapping("/{username}/{filename}/exists")
    public ResponseEntity<ResourceExistsView> resourceExists(
            @AuthenticationPrincipal User principal,
            @PathVariable String username,
            @PathVariable String filename
    ) {
        final boolean exists = this.imageService.exists(principal, username, filename);
        return ResponseEntity.ok(new ResourceExistsView(exists));
    }

    @DeleteMapping("/{username}/{filename}")
    public ResponseEntity<Object> deleteImage(
            @AuthenticationPrincipal User principal,
            @PathVariable String username,
            @PathVariable String filename
    ) throws IOException {
        final User owner = this.userService.getUser(username);
        final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
        this.imageService.deleteImage(image, principal);
        return ResponseEntity.noContent().build();
    }

}
