package com.aksenov.monitor.views.city;

import com.aksenov.monitor.data.City;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.services.CityService;
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.datepicker.DatePicker;
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.data.value.ValueChangeMode;
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 com.vaadin.flow.router.RouteAlias;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;

@PageTitle("City")
@Route("/:cityID?/:action?(edit)")
@Menu(order = 0, icon = LineAwesomeIconUrl.CITY_SOLID)
@RouteAlias("")
public class CityView extends Div implements BeforeEnterObserver {

    private final String CITY_ID = "cityID";
    private final String CITY_EDIT_ROUTE_TEMPLATE = "/%s/edit";

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

    private TextField name;

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

    private final Button delete = new Button("Delete");

    private final BeanValidationBinder<City> binder;

    private City city;

    private List<City> cities;

    private final CityService cityService;

    private ListDataProvider<City> dataProvider;

    public CityView(CityService cityService) {
        this.cityService = cityService;
        addClassNames("city-view");

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

        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);

        add(splitLayout);

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

        cities = cityService.list();
        dataProvider = new ListDataProvider<>(cities);
        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(CITY_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(CityView.class);
            }
        });

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

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

        binder.bindInstanceFields(this);

        addFilterRow();

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

        delete.addClickListener(e -> {
            try {
                if (this.city == null) {
                    return;
                }


                binder.writeBean(this.city);
                cityService.delete(this.city.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(CityView.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");
            }
        });

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

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

                binder.writeBean(this.city);
                cityService.save(this.city);
                clearForm();
                refreshGrid();
                Notification.show("Data updated");
                UI.getCurrent().navigate(CityView.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> cityId = event.getRouteParameters().get(CITY_ID).map(Long::parseLong);
        if (cityId.isPresent()) {
            Optional<City> cityFromBackend = cityService.get(cityId.get());
            if (cityFromBackend.isPresent()) {
                populateForm(cityFromBackend.get());
            } else {
                Notification.show(String.format("The requested city was not found, ID = %s", cityId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(CityView.class);
            }
        }
    }

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

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

            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(city -> city.getName().toLowerCase().contains(filterValue.toLowerCase()));
            }
            // Clear other filters
            clearOtherFilters(nameFilter);
            clearForm();
            refreshGrid();
        });
        nameFilter.setPlaceholder("Filter");
        nameFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("name")).setComponent(nameFilter);
    }

    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != nameFilter) {
            nameFilter.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");
        formLayout.add(name);

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

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


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

        dataProvider.setFilter(currentFilter);

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

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

    private void populateForm(City value) {
        this.city = value;
        binder.readBean(this.city);

    }

    private TextField nameFilter;
}