package sklep.rest;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import sklep.model.Product;
import sklep.photo.PhotoUtil;
import sklep.repository.ProductRepository;

/* RestController to jest taki Controller, który nie uzywa szablonów, tylko wyniki metod odsyła w treści odpowiedzi do klienta.
 * Inaczej mówiąc działa tak, jakby każda metoda miała @ResponseBody.
 * 
 * Najczęściej na poziomie klasy umieszcza się też @RequestMapping z ogólnym adresem, pod którym działa cała ta klasa.
 * Metody wewnątrz mogą mieć podany "dalszy ciąg adresu".
 */
@RestController
@RequestMapping("/products")
public class ProductController {
	
	private ProductRepository productRepository;
    private PhotoUtil photoUtil;
    
	// W tej klasie stosujemy wstrzykiwanie poprzez konstruktor,
	// czyli nie piszemy @Autowired przed polem productRepository,
	// tylko tworzymy konstruktor, który wymaga podania tego obiektu.
	public ProductController(ProductRepository productRepository, PhotoUtil photoUtil) {
		this.productRepository = productRepository;
		this.photoUtil = photoUtil;
	}
	
	@GetMapping
	public List<Product> getAllProducts() {
		return productRepository.findAll();
	}

	@GetMapping("/{id}")
	public Product getOneProduct(@PathVariable Integer id) {
		Optional<Product> product = productRepository.findById(id);
		return product.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Brak produktu nr " + id));
	}

	/*
	 * Tylko jako przykład możliwości: odczyt wybranego pola z rekordu. To twórca
	 * usługi decyduje o tym, jakie adresy będą dostępne i co pod nimi będzie. Jeśli
	 * uważamy, że klientom "przyda się" bezpośredni dostęp do wybranego pola, to
	 * możemy stworzyć taką metodę, ale nie ma żadnego obowiązku, aby robić to dla
	 * wszystkich pól.
	 */
	@GetMapping("/{id}/price")
	public BigDecimal getPrice(@PathVariable("id") Integer productId) {
		return getOneProduct(productId).getPrice();
	}

	/*
	 * Operacja PUT służy do zapisania danych POD PODANYM ADRESEM. Na przykład PUT
	 * products/2/price z wartością 100 powinno ustawić w produkcie nr 2 cenę 100.
	 * Jeśli PUT zadziała, to następnie GET wysłany pod ten sam adres powinien
	 * odczytać te same dane, które PUT zapisał. (być może w innym formacie - to
	 * inny temat)
	 * 
	 * PUT najczęściej jest używany do aktualizacji istniejących danych
	 * (pojedynczych wartości albo całych rekordów), ale może być też użyty do
	 * zapisania nowych danych. To, co najważniejsze, to fakt, że PUT zapisuje dane
	 * pod konkretnym adresem, do którego jest wysyłany.
	 */
	@PutMapping("/{id}/price")
	public void setPrice(@PathVariable("id") Integer productId, @RequestBody BigDecimal newPrice) {
		Optional<Product> optionalProduct = productRepository.findById(productId);
		if (optionalProduct.isPresent()) {
			Product product = optionalProduct.get();
			product.setPrice(newPrice);
			productRepository.save(product);
		} else {
			throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Brak produktu nr " + productId);
		}
	}

	/*
	 * PUT może również służyć do zapisania całego rekordu, ale zwn, że musi być
	 * skierowany pod ten adres, pod którym rekord zostanie faktycznie zapisany, w
	 * praktyce PUT jest uzywany do aktualizacji rekordów (UPDATE).
	 * 
	 * Aby w aplikacji Springowej, w której jest właczone security, działały zapytania POST i PUT,
	 * trzeba wyłączyć zabezpieczenie "CSRF":
	 * .and().csrf().disable()
	 */
	@PutMapping("/{id}")
	public void update(@PathVariable("id") Integer productId, @RequestBody Product product) {
		// Aby mieć pewność, że zapisujemu produkt o typ ID, wpisuję productId z URL-a.
		// Ewentualnie możnaby jeszcze sprawdzić czy rekord istnieje, czy ID się zgadza
		// i jeśli coś jest nie tak, to wyrzucić wyjątek.
		product.setProductId(productId);
		productRepository.save(product);
	}

	/*
	 * POST jest najbardziej ogólną metodą HTTP; oznacza, że klient
	 * "wysyła jakieś dane na serwer", a serwer odsyła jakąś odpowiedź. W zasadzie
	 * POST może słuzyć do wszystkiego.
	 * 
	 * W praktyce POST bardzo często służy do dodawania nowych rekordów, ponieważ
	 * gdy tworzymy nowy rekord, to nie znamy z góry jego ID i nie wiemy pod jakim
	 * URL-em zostanie zapisany (nie da się więc użyć PUT). Stąd wzięła REST-owa
	 * konwencja, że aby dodać nowy rekord do katalogu, wysyła się POST z danymi
	 * tego rekordu pod ogólny adres całego katalogu.
	 */
	@PostMapping
	public Product insert(@RequestBody Product product) {
		// Aby mieć pewność, że zapytanie tworzy nowy rekord, ustawwiam productId na
		// null.
		product.setProductId(null);
		productRepository.save(product);
		/*
		 * Operacja save (a wewnętrznie persist z Hibernate) spowoduje ustawienie nowego
		 * ID w obiekcie. Warto taką informację przekazać klientowi. Można: 1) odesłać
		 * uzupełniony rekord (i tak zrobimy tutaj), 2) odesłać "małego JSON-a" z
		 * informacją o tym ID (i innymi informacjami, które serwer chce przekazać
		 * klientowi) 3) tylko nagłówek Location z URL-em nowego rekordu (to zobaczymy w
		 * wersji JAX-RS).
		 */
		return product;
	}

	@DeleteMapping("/{id}")
	public void delete(@PathVariable("id") Integer productId) {
		try {
			productRepository.deleteById(productId);
		} catch (EmptyResultDataAccessException e) {
			throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Brak produktu nr " + productId);
		}
	}
 	
    /* Większość komunikacji w usługach REST odbywa się w formacie JSON,
     * ale czasami używany jest też format XML,
     * a dla niektórych danych stosujemy bezpośrednio jakiś format specjalny, np. PNG, JPG dla obrazów, PDF dla wydruków itp.
     */
    @GetMapping(path="/{id}/photo", produces="image/jpeg")
    public byte[] getPhoto(
            @PathVariable("id") Integer productId) {
        return photoUtil.readBytes(productId);
    }
}

