package com.aksenov.monitor.views.sales;

import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.data.Monitor;
import com.aksenov.monitor.data.Sales;
import com.aksenov.monitor.services.CustomerService;
import com.aksenov.monitor.services.MonitorService;
import com.aksenov.monitor.services.SalesService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.converter.StringToIntegerConverter;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;

@PageTitle("Sales")
@Route("sales/:salesID?/:action?(edit)")
@Menu(order = 4, icon = LineAwesomeIconUrl.DOLLAR_SIGN_SOLID)
public class SalesView extends Div implements BeforeEnterObserver {

    private final String SALES_ID = "salesID";
    private final String SALES_EDIT_ROUTE_TEMPLATE = "sales/%s/edit";

    private final Grid<Sales> grid = new Grid<>(Sales.class, false);

    private ComboBox<Monitor> monitor;
    private ComboBox<Customer> customer;
    private DateTimePicker saleDate;
    private TextField quantity;
    private TextField totalPrice;

    private final Button cancel = new Button("Cancel");
    private final Button save = new Button("Save");
    private final Button delete = new Button("Delete");

    private final BeanValidationBinder<Sales> binder;

    private Sales sales;

    private List<Sales> salesList;

    private ListDataProvider<Sales> dataProvider;
    private final SalesService salesService;
    private final MonitorService monitorService;
    private final CustomerService customerService;

    public SalesView(SalesService salesService, MonitorService monitorService, CustomerService customerService) {
        this.salesService = salesService;
        this.monitorService = monitorService;
        this.customerService = customerService;
        addClassNames("sales-view");

        // Create UI
        SplitLayout splitLayout = new SplitLayout();

        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);

        add(splitLayout);

        // Configure Grid
        grid.addColumn("monitor.model").setAutoWidth(true).setHeader("Monitor");
        grid.addColumn("customer.name").setAutoWidth(true).setHeader("Customer");
        grid.addColumn("saleDate").setAutoWidth(true);
        grid.addColumn("quantity").setAutoWidth(true);
        grid.addColumn("totalPrice").setAutoWidth(true);
        //grid.setItems(query -> salesService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);


        salesList = salesService.list();
        dataProvider = new ListDataProvider<>(salesList);
        grid.setDataProvider(dataProvider);

        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(SALES_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(SalesView.class);
            }
        });

        // Configure Form
        binder = new BeanValidationBinder<>(Sales.class);

        // Bind fields. This is where you'd define e.g. validation rules

        binder.forField(monitor).bind("monitor");
        binder.forField(customer).bind("customer");
        binder.forField(saleDate).bind("saleDate");
        binder.forField(quantity).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("quantity");
        binder.forField(totalPrice).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("totalPrice");


        addFilterRow();

        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });

        save.addClickListener(e -> {
            try {
                if (this.sales == null) {
                    this.sales = new Sales();
                }

                if (monitor.isEmpty() || customer.isEmpty() || saleDate.isEmpty()
                        || quantity.isEmpty() ){
                    Notification.show("Please fill all required fields");
                    return;
                }

                // Check if the binder has errors before writing
                if (binder.validate().isOk()) {
                    binder.writeBean(this.sales);
                    salesService.save(this.sales);
                    clearForm();
                    refreshGrid();
                    Notification.show("Data updated");
                    UI.getCurrent().navigate(SalesView.class);
                }else{
                    Notification.show("Please fill all required fields");
                }

            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });

        delete.addClickListener(e -> {
            try {
                if (this.sales == null) {
                    return;
                }
                binder.writeBean(this.sales);
                salesService.delete(this.sales.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(SalesView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Long> salesId = event.getRouteParameters().get(SALES_ID).map(Long::parseLong);
        if (salesId.isPresent()) {
            Optional<Sales> salesFromBackend = salesService.get(salesId.get());
            if (salesFromBackend.isPresent()) {
                populateForm(salesFromBackend.get());
            } else {
                Notification.show(String.format("The requested sales was not found, ID = %s", salesId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(SalesView.class);
            }
        }
    }

    private void addFilterRow() {
        HeaderRow headerRow = grid.appendHeaderRow();

        // Filter for monitor column
        monitorIdFilter = new TextField();
        monitorIdFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getMonitor()).contains(filterValue));
            }
            clearOtherFilters(monitorIdFilter);
            clearForm();
            refreshGrid();
        });
        monitorIdFilter.setPlaceholder("Filter");
        monitorIdFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("monitor.model")).setComponent(monitorIdFilter);

        // Filter for customerId column
        customerIdFilter = new TextField();
        customerIdFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getCustomer()).contains(filterValue));
            }
            clearOtherFilters(customerIdFilter);
            clearForm();
            refreshGrid();
        });
        customerIdFilter.setPlaceholder("Filter");
        customerIdFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("customer.name")).setComponent(customerIdFilter);


        // Filter for saleDate column
        saleDateFilter = new TextField();
        saleDateFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getSaleDate()).contains(filterValue));
            }
            clearOtherFilters(saleDateFilter);
            clearForm();
            refreshGrid();
        });
        saleDateFilter.setPlaceholder("Filter");
        saleDateFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("saleDate")).setComponent(saleDateFilter);

        // Filter for quantity column
        quantityFilter = new TextField();
        quantityFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getQuantity()).contains(filterValue));
            }
            clearOtherFilters(quantityFilter);
            clearForm();
            refreshGrid();
        });
        quantityFilter.setPlaceholder("Filter");
        quantityFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("quantity")).setComponent(quantityFilter);


        // Filter for totalPrice column
        totalPriceFilter = new TextField();
        totalPriceFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getTotalPrice()).contains(filterValue));
            }
            clearOtherFilters(totalPriceFilter);
            clearForm();
            refreshGrid();
        });
        totalPriceFilter.setPlaceholder("Filter");
        totalPriceFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("totalPrice")).setComponent(totalPriceFilter);
    }

    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != monitorIdFilter) {
            monitorIdFilter.clear();
        }
        if (currentFilter != customerIdFilter) {
            customerIdFilter.clear();
        }
        if (currentFilter != saleDateFilter) {
            saleDateFilter.clear();
        }
        if (currentFilter != quantityFilter) {
            quantityFilter.clear();
        }
        if (currentFilter != totalPriceFilter) {
            totalPriceFilter.clear();
        }
    }

    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("editor-layout");

        Div editorDiv = new Div();
        editorDiv.setClassName("editor");
        editorLayoutDiv.add(editorDiv);

        FormLayout formLayout = new FormLayout();

        monitor = new ComboBox<Monitor>("Monitor");
        monitor.setItemLabelGenerator(currMonitor -> String.valueOf(currMonitor.getId()));
        monitor.setAllowCustomValue(false);
        monitor.setRequired(true);  // Make it required
        monitor.addValueChangeListener(event -> {
            if(event.getValue() != null) {
                calculateTotalPrice();
            } else {
                totalPrice.clear();
            }
        });
        monitor.addFocusListener(event -> {
            List<Monitor> manufacturerList = monitorService.list();
            monitor.setItems(manufacturerList);
        });
        monitor.setItemLabelGenerator(monitor ->
                monitor != null ? monitor.getModel() : "");

        customer = new ComboBox<Customer>("Customer");
        customer.setItemLabelGenerator(currMonitor -> String.valueOf(currMonitor.getId()));
        customer.setAllowCustomValue(false);
        customer.setRequired(true);  // Make it required
        customer.addFocusListener(event -> {
            List<Customer> customerList = customerService.list();
            customer.setItems(customerList);
        });
        customer.setItemLabelGenerator(customer ->
                customer != null ? customer.getName() : "");

        saleDate = new DateTimePicker("Sale Date");
        saleDate.setStep(Duration.ofSeconds(1));
        saleDate.setRequiredIndicatorVisible(true); // Make it required
        quantity = new TextField("Quantity");
        quantity.setRequired(true); // Make it required
        quantity.addValueChangeListener(event -> calculateTotalPrice());
        totalPrice = new TextField("Total Price");
        totalPrice.setReadOnly(true);
        formLayout.add(monitor, customer, saleDate, quantity, totalPrice);

        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);

        splitLayout.addToSecondary(editorLayoutDiv);
    }

    private void calculateTotalPrice() {
        if(monitor.getValue() != null && !quantity.isEmpty()) {
            Integer quantityValue = Integer.parseInt(quantity.getValue());
            Integer monitorPrice = monitor.getValue().getPrice();
            Integer totalPriceValue = quantityValue * monitorPrice;

            totalPrice.setValue(String.valueOf(totalPriceValue));
        } else {
            totalPrice.clear();
        }
    }

    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("button-layout");
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }

    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setClassName("grid-wrapper");
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }

    private void refreshGrid() {
        SerializablePredicate<Sales> currentFilter = dataProvider.getFilter();

        salesList = salesService.list();

        dataProvider = new ListDataProvider<>(salesList);
        grid.setDataProvider(dataProvider);

        dataProvider.setFilter(currentFilter);
        dataProvider.refreshAll();
    }

    private void clearForm() {
        populateForm(null);
    }

    private void populateForm(Sales value) {
        this.sales = value;
        // Загрузить список городов
        List<Monitor> monitorList = monitorService.list();
        // Установить список городов в ComboBox
        monitor.setItems(monitorList);

        List<Customer> customerList = customerService.list();
        // Установить список городов в ComboBox
        customer.setItems(customerList);
        // Установить значения клиента
        binder.readBean(this.sales);


        if (value != null && value.getMonitor() != null) {
            monitor.setValue(value.getMonitor());
        } else {
            monitor.clear();
        }

        if (value != null && value.getCustomer() != null) {
            customer.setValue(value.getCustomer());
        } else {
            customer.clear();
        }

        if(value != null && value.getSaleDate() != null) {
            saleDate.setValue(value.getSaleDate()); // Set the date from the Sales entity
        } else {
            saleDate.clear();
        }
        calculateTotalPrice();
    }

    private TextField monitorIdFilter;
    private TextField customerIdFilter;
    private TextField saleDateFilter;
    private TextField quantityFilter;
    private TextField totalPriceFilter;
}