package sklep.rest;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
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 java.math.BigDecimal;
import java.net.URI;
import java.util.List;

/* 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", "text/plain;charset=UTF-8"})
    // @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
    public List<Product> allProducts() throws DBException {
        try(DBConnection db = DBConnection.open()) {
            ProductDAO productDAO = db.productDAO();
            return productDAO.readAll();
        }
    }

    @GET
    @Produces({"application/xml", "application/pdf"})
    public ProductList allProductsXml() throws DBException {
        return new ProductList(allProducts());
    }

    // 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 = allProducts();
        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;charset=UTF-8", "application/pdf"})
    // przykładowo /api/products/3
    public Product oneProduct(@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 = oneProduct(productId);
        return "<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>";
    }

    // 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 saveProduct(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();
    }

    // Nie praktykuje się tego zbyt często, ale można zdefiniować dedykowane metody dające dostęp do poszczególnych pól obiektu (aby nie transferować całego rekordu, gdy potrzebna tylko jedna informacja)
    @GET
    @Path("/{id}/price")
    // przykładowo /api/products/3/price
    @Produces({"application/json", "text/plain"})
    public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
        return oneProduct(productId).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
    @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();
        }
    }

    // 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
    @Path("/{id}/photo")
    // przykładowo /api/products/3/photo
    @Produces("image/jpeg")
    public byte[] getPhoto(@PathParam("id") int productId) throws DBException, RecordNotFound {
        return PhotoUtil.readBytes(productId);
    }

    @PUT
    @Path("/{id}/photo")
    @Consumes("image/jpeg")
    public void getPhoto(@PathParam("id") int productId, byte[] bajty) {
        PhotoUtil.writeBytes(productId, bajty);
    }
}
