ShelterController.java

package org.petify.shelter.controller;

import org.petify.shelter.dto.AdoptionResponse;
import org.petify.shelter.dto.ShelterRequest;
import org.petify.shelter.dto.ShelterResponse;
import org.petify.shelter.exception.RoutingException;
import org.petify.shelter.service.AdoptionService;
import org.petify.shelter.service.PetService;
import org.petify.shelter.service.ShelterService;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.AllArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
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.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@AllArgsConstructor
@RestController
@RequestMapping("/shelters")
public class ShelterController {
    private final ShelterService shelterService;
    private final PetService petService;
    private final AdoptionService adoptionService;

    @GetMapping()
    public ResponseEntity<Page<ShelterResponse>> getShelters(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {

        Pageable pageable = PageRequest.of(page, size);
        return ResponseEntity.ok(shelterService.getShelters(pageable));
    }

    @GetMapping("/{id}/owner")
    @PreAuthorize("hasAnyRole('USER', 'SHELTER', 'ADMIN')")
    public ResponseEntity<String> owner(@PathVariable Long id) {
        return ResponseEntity.ok(petService.getOwnerUsernameByPetId(id));
    }

    @PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
    @PostMapping()
    public ResponseEntity<?> addShelter(
            @Valid @RequestPart ShelterRequest shelterRequest,
            @RequestPart(value = "imageFile", required = false) MultipartFile imageFile,
            @AuthenticationPrincipal Jwt jwt) throws IOException {

        String username = jwt != null ? jwt.getSubject() : null;
        ShelterResponse shelter = shelterService.createShelter(shelterRequest, imageFile, username);

        return new ResponseEntity<>(shelter, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<?> getShelterById(@PathVariable("id") Long id) {
        return ResponseEntity.ok(shelterService.getShelterById(id));
    }

    @GetMapping("/by-owner/{username}")
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    public ResponseEntity<ShelterResponse> getShelterByOwner(@PathVariable String username) {
        ShelterResponse shelter = shelterService.getShelterByOwnerUsername(username);
        return ResponseEntity.ok(shelter);
    }

    @GetMapping("/my-shelter")
    @PreAuthorize("hasRole('SHELTER')")
    public ResponseEntity<ShelterResponse> getMyShelter(@AuthenticationPrincipal Jwt jwt) {
        String username = jwt.getSubject();
        ShelterResponse shelter = shelterService.getShelterByOwnerUsername(username);
        return ResponseEntity.ok(shelter);
    }

    @GetMapping("/my-shelter/id")
    @PreAuthorize("hasRole('SHELTER')")
    public ResponseEntity<Long> getMyShelterIdAndVerifyOwnership(@AuthenticationPrincipal Jwt jwt) {
        String username = jwt != null ? jwt.getSubject() : null;
        ShelterResponse shelter = shelterService.getShelterByOwnerUsername(username);
        return ResponseEntity.ok(shelter.id());
    }

    @GetMapping("/{id}/pets")
    public ResponseEntity<?> getPetsByShelterId(
            @PathVariable("id") Long id,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {

        Pageable pageable = PageRequest.of(page, size);
        return ResponseEntity.ok(petService.getAllShelterPets(id, pageable));
    }

    @PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
    @PutMapping("/{id}")
    public ResponseEntity<?> updateShelter(@PathVariable("id") Long id,
                                           @Valid @RequestPart ShelterRequest shelterRequest,
                                           @RequestPart(value = "imageFile", required = false) MultipartFile imageFile,
                                           @AuthenticationPrincipal Jwt jwt) throws IOException {

        verifyShelterOwnership(id, jwt);

        ShelterResponse updatedShelter = shelterService.updateShelter(shelterRequest, imageFile, id);
        return ResponseEntity.ok(updatedShelter);
    }

    @PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteShelter(@PathVariable("id") Long id,
                                           @AuthenticationPrincipal Jwt jwt) {

        verifyShelterOwnership(id, jwt);

        shelterService.deleteShelter(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
    @GetMapping("/{id}/adoptions")
    public ResponseEntity<List<AdoptionResponse>> getShelterAdoptionForms(
            @PathVariable Long id,
            @AuthenticationPrincipal Jwt jwt) {

        verifyShelterOwnership(id, jwt);

        List<AdoptionResponse> forms = adoptionService.getShelterAdoptionForms(id);
        return ResponseEntity.ok(forms);
    }

    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping("/{shelterId}/activate")
    public ResponseEntity<?> activateShelter(@PathVariable Long shelterId) {
        shelterService.activateShelter(shelterId);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping("/{shelterId}/deactivate")
    public ResponseEntity<?> deactivateShelter(@PathVariable Long shelterId) {
        shelterService.deactivateShelter(shelterId);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping("/{shelterId}/route")
    public ResponseEntity<?> shelterRoute(
            @PathVariable Long shelterId,
            @RequestParam(required = true) @Min(-90) @Max(90) Double latitude,
            @RequestParam(required = true) @Min(-180) @Max(180) Double longitude) {

        try {
            ShelterResponse shelter = shelterService.getShelterById(shelterId);

            if (shelter == null) {
                return ResponseEntity.notFound().build();
            }

            if (shelter.latitude() == null || shelter.longitude() == null) {
                return ResponseEntity.badRequest().body(
                        Map.of("error", "Shelter has invalid coordinates"));
            }

            String routeJson = shelterService.getRouteToShelter(latitude, longitude, shelter);

            new ObjectMapper().readTree(routeJson);

            return ResponseEntity.ok()
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(routeJson);

        } catch (IOException | InterruptedException e) {
            return ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("error", "Routing service unavailable"));
        } catch (RoutingException e) {
            throw new RuntimeException(e);
        }
    }

    private void verifyShelterOwnership(Long shelterId, Jwt jwt) {
        String username = jwt != null ? jwt.getSubject() : null;
        ShelterResponse shelter = shelterService.getShelterById(shelterId);

        if (!shelter.ownerUsername().equals(username)) {
            throw new AccessDeniedException("You are not the owner of this shelter");
        }
    }

    /**
     * Sprawdza czy schronisko istnieje i jest aktywne
     */
    @GetMapping("/{shelterId}/validate")
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public ResponseEntity<Void> validateShelter(@PathVariable Long shelterId) {
        HttpStatus status = shelterService.validateShelterForDonations(shelterId);
        return ResponseEntity.status(status).build();
    }

    /**
     * Sprawdza czy zwierzę istnieje w danym schronisku i czy można na nie wpłacać
     */
    @GetMapping("/{shelterId}/pets/{petId}/validate")
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public ResponseEntity<Void> validatePetInShelter(
            @PathVariable Long shelterId,
            @PathVariable Long petId) {

        HttpStatus status = petService.validatePetForDonations(shelterId, petId);
        return ResponseEntity.status(status).build();
    }
}