package sklep.rest;

import java.math.BigDecimal;
import java.net.URI;
import java.util.List;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.model.ProductList;
import sklep.photo.PhotoUtil;

/* JAX-RS - część Javy EE
 * Aplikacja (projekt) składa się z wielu "resource classes", z których każda obsługuje pewien rodzaj encji
 * np. pod adresem /products działa ta klasa RProduct, która zajmuje się obsługą produktów
 * a pod adresem /orders działa ROrder, która zajmuje się zamówieniami.
 * Przykładowa dobra (szczegółowa) dokumentacja: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest3x/index.html
 *  
 *  W usługach REST struktura adresów URL odpowiada logicznej strukturze danych.
 * Typowe jest, że * pod adresem "katalogu", np. /products , dostępna jest lista wszystkich rekordów
 * (lub ogólne informacje, lub jakiś sposób przeglądania - gdyby było ich za dużo do zwrócenia na raz)
 * a idąc dalej wgłąb ścieżki przechodzimy do "pojedynczych rekordów", np. /products/2
 * 
 * W tej klasie wejdziemy nawet do wnętrza produktów i udostępnimy osobno cenę
 * /products/2/price
 * W praktyce nie robi się tego zbyt często, ale tu zobaczymy jako przykład możliwości.
 * 
 * To od programisty (twórcy usługi) zależy jakie adresy i jakie operacje pod tymi adresami udostępnia.
 * 
 * Ta klasa udostępnia (i przyjmuje) dane produktów w różnych formatach.
 * Gdy w Produces jest wiele formatów, to klient może wybrać za pomocą nagłówka Accept
 * Gdy w Consumes jest wiele formatów, to klient może przysłać dane w dowolnym z nich (nagłówek Content-Type)
 *
 */

@Path("/products")
public class RProducts {

	@GET
	@Produces({"application/json", "application/xml", "text/plain", "application/pdf"})
	public ProductList wszystkieProdukty() throws DBException {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			return new ProductList(productDAO.readAll());
		}
	}
	
	// Może też być tak, że kilka metod działa pod tym samym adresem, ale służą one do tworzenia odpowiedzi w różnych formatach.
	// Przykład: tworzenie HTML w oddzielnej metodzie
	@GET
	@Produces("text/html;charset=UTF-8")
	public String wszystkieProduktyHTML() throws DBException {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			List<Product> products = productDAO.readAll();
			StringBuilder txt = new StringBuilder("<!DOCTYPE html>\n<html><body>\n");
			txt.append("<h1>Lista produktów</h1>\n");
			for(Product product : products) {
				txt.append(product.toHtml()).append('\n');
			}
			txt.append("</body></html>");
			return txt.toString();
		}
	}

	@GET
	@Path("/{id}")
	@Produces({"application/json", "application/xml", "text/plain", "application/pdf"})
	public Product jedenProdukt(@PathParam("id") int productId) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			return productDAO.findById(productId);
		}
	}
	
	@Path("/{id}")
	@GET
	@Produces("text/html;charset=UTF-8")
	public String jedenProduktHTML(@PathParam("id") int productId) throws DBException, RecordNotFound {
		Product product = jedenProdukt(productId);		
		return "<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>";
	}
	
	@GET
	@Path("/{id}/price")
	@Produces({"application/json", "text/plain"})
	public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
		return jedenProdukt(productId).getPrice();
	}
	
	// W zapytaniach typu PUT i POST klient wysyła dane na serwer (treść zapytania: "body" / "entity" / "content" ...)
	// W JAX-RS metoda może posiadać tylko jeden parametr bez adnotacji i to właśnie przez ten parametr przekazywana jest treść zapytania
	// (te dane, które przysłał klient). Format tych danych opisuje adnotacja @Consumes
	@PUT
	@Path("/{id}/price")
	@Consumes({"application/json", "text/plain"})
	public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			Product product = productDAO.findById(productId);
			product.setPrice(newPrice);
			productDAO.save(product);
			db.commit();
		}
	}
	
	// W praktyce REST metoda POST jest używana do dodawania nowych rekordów do katalogu.
	// Może być także używana w innych celach - gdy klient ma "przysłać dane na serwer", a serwer coś z tym zrobi (podobnie jak to było w SOAP).
	@POST
	@Consumes({"application/json", "application/xml"})
	@Produces({"application/json", "application/xml"})
	public Response zapiszProdukt(Product product) throws DBException {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			productDAO.save(product);
			db.commit();
			
			URI uri = UriBuilder.fromResource(RProducts.class).path("/{id}").build(product.getProductId());
			return Response.created(uri).build();
		}
	}
	
	// PUT vs POST
    // PUT powinniśmy używać wtedy, gdy z góry wiadomo, pod jakim adresem zostaną zapisane dane.
    // W praktyce PUT używa się najczęściej do aktualizacji istniejących danych, ale teoretycznie PUT
    // można też użyć do zapisania nowych danych pod konkretnym adresem.
    // Gdy wyślemy dane za pomocą PUT pod adres, to następnie GET z tego samego adresu powinien odczytać dane, które wysłał PUT (być może w innym formacie).
    
    // POST używajmy wtedy, gdy nie wiemy z góry pod jakim adresem dane zostaną zapisane, np. gdy id rekordu jest generowane z sekwencji.
    // Taka metoda powinna dać w odpowiedzi informację o ID utworzonego rekordu.
    // Kilka możliwości:
    // 1) (w tej klasie) - odesłanie uzupełnionego rekordu - trochę niewydajne, ale wygodne
    // 2) minimalny dokumencik JSON, w którego wnętrzu jest zawarta ta informacja (tak robi wiele usług w praktyce, np. PayU)
    // 3) (zobaczymy jeszcze) - odesłać odpowiedź typu Created z nagłówkiem Location - najlepsze z punktu widzenia standardów/dobrych praktyk

	@DELETE
	@Path("/{id}")
	public void usun(@PathParam("id") int productId) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			productDAO.delete(productId);
			db.commit();
		}
	}
	
	@GET
	@Path("/{id}/photo")
	@Produces("image/jpeg")
	public byte[] foto(@PathParam("id") int productId) throws DBException, RecordNotFound {
		return PhotoUtil.readBytes(productId);
	}
	
}

