package com.aksenov.monitor.views.customer;

import com.aksenov.monitor.data.City;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.services.CityService;
import com.aksenov.monitor.services.CustomerService;
import com.aksenov.monitor.views.city.CityView;
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.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.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.util.List;
import java.util.Optional;
import java.util.logging.Filter;

import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;

@PageTitle("Customer")
@Route("customer/:customerID?/:action?(edit)")
@Menu(order = 1, icon = LineAwesomeIconUrl.ADDRESS_CARD)
public class CustomerView extends Div implements BeforeEnterObserver {

    private final String CUSTOMER_ID = "customerID";
    private final String CUSTOMER_EDIT_ROUTE_TEMPLATE = "customer/%s/edit";

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

    private TextField name;
    private TextField email;
    private ComboBox<City> city;

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

    private final BeanValidationBinder<Customer> binder;

    private Customer customer;

    private List<Customer> customers;

    private final CustomerService customerService;

    private final CityService cityService;

    private ListDataProvider<Customer> dataProvider;

    public CustomerView(CustomerService customerService, CityService cityService) {
        this.customerService = customerService;
        this.cityService = cityService;
        addClassNames("customer-view");

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

        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);

        add(splitLayout);

        // Configure Grid
        grid.addColumn("name").setAutoWidth(true);
        grid.addColumn("email").setAutoWidth(true);
        grid.addColumn("city.name").setAutoWidth(true).setHeader("City");
        //grid.setItems(query -> customerService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);

        customers = customerService.list();
        dataProvider = new ListDataProvider<>(customers);
        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(CUSTOMER_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(CustomerView.class);
            }
        });

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

        addFilterRow();

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

        binder.bindInstanceFields(this);

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

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

                if (name.isEmpty() || email.isEmpty() || city.isEmpty() ) {
                    Notification.show("Please fill all required fields");
                    return;
                }

                binder.writeBean(this.customer);
                customerService.save(this.customer);
                clearForm();
                refreshGrid();
                Notification.show("Data updated");
                UI.getCurrent().navigate(CustomerView.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");
            }
        });

        delete.addClickListener(e -> {
            try {
                if (this.city == null) {
                    return;
                }
                binder.writeBean(this.customer);
                customerService.delete(this.customer.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(CustomerView.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> customerId = event.getRouteParameters().get(CUSTOMER_ID).map(Long::parseLong);
        if (customerId.isPresent()) {
            Optional<Customer> customerFromBackend = customerService.get(customerId.get());
            if (customerFromBackend.isPresent()) {
                populateForm(customerFromBackend.get());
            } else {
                Notification.show(String.format("The requested customer was not found, ID = %s", customerId.get()),
                        3000, Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(CustomerView.class);
            }
        }
    }

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

        // Filter for name column
        nameFilter = new TextField();
        nameFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();

            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(customer -> customer.getName().toLowerCase().contains(filterValue.toLowerCase()));
            }

            clearOtherFilters(nameFilter);
            clearForm();
            refreshGrid();
        });
        nameFilter.setPlaceholder("Filter");
        nameFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("name")).setComponent(nameFilter);

        // Filter for email column
        emailFilter = new TextField();
        emailFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();

            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(customer -> customer.getEmail().toLowerCase().contains(filterValue.toLowerCase()));
            }

            clearOtherFilters(emailFilter);
            clearForm();
            refreshGrid();
        });
        emailFilter.setPlaceholder("Filter");
        emailFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("email")).setComponent(emailFilter);

        // Filter for cityName column
        cityFilter = new TextField();
        cityFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            dataProvider.setFilter(customer -> customer.getCityName() != null && customer.getCityName().toLowerCase().contains(filterValue.toLowerCase()));
            clearOtherFilters(cityFilter);
            clearForm();
            refreshGrid();
        });
        cityFilter.setPlaceholder("Filter");
        cityFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("city.name")).setComponent(cityFilter);
    }

    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != nameFilter) {
            nameFilter.clear();
        }
        if (currentFilter != emailFilter) {
            emailFilter.clear();
        }
        if (currentFilter != cityFilter) {
            cityFilter.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();
        name = new TextField("Name");
        email = new TextField("Email");

        city = new ComboBox<City>("City");
        city.setItemLabelGenerator(currCity -> String.valueOf(currCity.getId()));
        city.setAllowCustomValue(false);
        city.addFocusListener(event -> {
            List<City> cityList = cityService.list();
            city.setItems(cityList);
        });
        city.setItemLabelGenerator(City::getName);

        formLayout.add(name, email, city);

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

        splitLayout.addToSecondary(editorLayoutDiv);
    }

    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<Customer> currentFilter = dataProvider.getFilter();

        // 2. Обновить список
        customers = customerService.list();


        // 3. Создать новый dataProvider и применить фильтр
        dataProvider = new ListDataProvider<>(customers);
        grid.setDataProvider(dataProvider);

        dataProvider.setFilter(currentFilter);

        //  grid.select(null);
        dataProvider.refreshAll();
    }

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

    private void populateForm(Customer value) {
        this.customer = value;
        // Загрузить список городов
        List<City> cityList = cityService.list();
        // Установить список городов в ComboBox
        city.setItems(cityList);
        // Установить значения клиента
        binder.readBean(this.customer);

        if (value != null && value.getCity() != null) {
            city.setValue(value.getCity());
        } else {
            city.clear();
        }
    }

    private TextField nameFilter;
    private TextField emailFilter;
    private TextField cityFilter;
}