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;

/* 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 readAll() 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 readAllHTML() throws DBException {
        List<Product> products = readAll().getProducts();
        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
    @Produces({"application/json", "application/xml", "text/plain", "application/pdf"})
    @Path("/{id}")
    public Product readOne(@PathParam("id") int productId) throws DBException, RecordNotFound {
        try(DBConnection db = DBConnection.open()) {
            ProductDAO productDAO = db.productDAO();
            return productDAO.findById(productId);
        }
    }
    
    @GET
    @Produces("text/html;charset=UTF-8")
    @Path("/{id}")
    public String readOneHTML(@PathParam("id") int productId) throws DBException, RecordNotFound {
        Product product = readOne(productId);      
        return "<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>";
    }
    
    // Dostęp do pojedynczego pola w rekordzie - nie ma obowiązku implementowania takich metod
    // i nie robi robi się tego zbyt często, ale pokazujemy, że można (gdyby klient często potrzebował dostępu do wybranego szczegółu)
    @GET
    @Produces({"application/json", "text/plain"})
    @Path("/{id}/price")
    public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
        try(DBConnection db = DBConnection.open()) {
            ProductDAO productDAO = db.productDAO();
            Product product = productDAO.findById(productId);
            return product.getPrice();
        }
    }
    
    // Operacja HTTP PUT zapisuje zasób pod podanym adresem.
    // 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
    @Consumes({"application/json", "text/plain"})
    @Path("/{id}/price")
    public void putPrice(@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.update(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
    // POST w tym miejscu jest lepszy niż PUT, bo zapisując nowy rekord, nie wiemy z góry jakie będzie będzie jego ID,
    // czyli nie wiemy, pod adresem zapisze się nowy produkt.
    // POST potrafi "dodać rekord do katalogu".
    @POST
    @Consumes({"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();
            // Pobranie adresu obsługiwanego przez klasę RProducts i dodanie id konkretnego produktu:
            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) 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) (teraz w tej wersji) - odesłać odpowiedź typu Created z nagłówkiem Location - najlepsze z punktu widzenia standardów/dobrych praktyk
    // Aby zwracać taie "techniczne" odpowiedzi, używa się klasy Response.

    @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
    @Produces("image/jpeg")
    @Path("/{id}/photo")
    // http://localhost:8080/PC32-RestSerwer/products/1/photo
    public byte[] photo(@PathParam("id") int productId) throws DBException, RecordNotFound {
        return PhotoUtil.readBytes(productId);
    }
    
}
