package com.aksenov.monitor.views.monitor;

import com.aksenov.monitor.data.Manufacturer;
import com.aksenov.monitor.data.Monitor;
import com.aksenov.monitor.services.ManufacturerService;
import com.aksenov.monitor.services.MonitorService;
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.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.util.List;
import java.util.Optional;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;

@PageTitle("Monitor")
@Route("monitor/:monitorID?/:action?(edit)")
@Menu(order = 3, icon = LineAwesomeIconUrl.TV_SOLID)
public class MonitorView extends Div implements BeforeEnterObserver {

    private final String MONITOR_ID = "monitorID";
    private final String MONITOR_EDIT_ROUTE_TEMPLATE = "monitor/%s/edit";

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

    private TextField model;
    private ComboBox<Manufacturer> manufacturer;
    private TextField screenSize;
    private TextField resolution;
    private TextField refreshrRate;
    private TextField price;

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

    private final BeanValidationBinder<Monitor> binder;

    private Monitor monitor;

    private List<Monitor> monitors;

    private final MonitorService monitorService;
    private final ManufacturerService manufacturerService;


    private ListDataProvider<Monitor> dataProvider;

    public MonitorView(MonitorService monitorService, ManufacturerService manufacturerService) {
        this.monitorService = monitorService;
        this.manufacturerService = manufacturerService;
        addClassNames("monitor-view");

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

        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);

        add(splitLayout);

        // Configure Grid
        grid.addColumn("model").setAutoWidth(true);
        grid.addColumn("manufacturer.name").setAutoWidth(true).setHeader("Manufacturer");
        grid.addColumn("screenSize").setAutoWidth(true);
        grid.addColumn("resolution").setAutoWidth(true);
        grid.addColumn("refreshrRate").setAutoWidth(true);
        grid.addColumn("price").setAutoWidth(true);
        //grid.setItems(query -> monitorService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);

        monitors = monitorService.list();
        dataProvider = new ListDataProvider<>(monitors);
        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(MONITOR_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(MonitorView.class);
            }
        });

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

        // Bind fields. This is where you'd define e.g. validation rules
        binder.forField(model).bind("model");
        binder.forField(manufacturer).bind("manufacturer");
        binder.forField(resolution).bind("resolution");
        binder.forField(screenSize).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("screenSize");
        binder.forField(refreshrRate).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("refreshrRate");
        binder.forField(price).withConverter(new StringToIntegerConverter("Only numbers are allowed")).bind("price");

        addFilterRow();

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

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

                if (model.isEmpty() || manufacturer.isEmpty() || screenSize.isEmpty() || resolution.isEmpty() ||
                        refreshrRate.isEmpty() || price.isEmpty()) {
                    Notification.show("Please fill all required fields");
                    return;
                }

                if (binder.validate().isOk()) {
                    binder.writeBean(this.monitor);
                    monitorService.save(this.monitor);
                    clearForm();
                    refreshGrid();
                    Notification.show("Data updated");
                    UI.getCurrent().navigate(MonitorView.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.monitor == null) {
                    return;
                }
                binder.writeBean(this.monitor);
                monitorService.delete(this.monitor.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(MonitorView.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> monitorId = event.getRouteParameters().get(MONITOR_ID).map(Long::parseLong);
        if (monitorId.isPresent()) {
            Optional<Monitor> monitorFromBackend = monitorService.get(monitorId.get());
            if (monitorFromBackend.isPresent()) {
                populateForm(monitorFromBackend.get());
            } else {
                Notification.show(String.format("The requested monitor was not found, ID = %s", monitorId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(MonitorView.class);
            }
        }
    }

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

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

            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(monitor -> monitor.getModel().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(modelFilter);
            clearForm();
            refreshGrid();
        });
        modelFilter.setPlaceholder("Filter");
        modelFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("model")).setComponent(modelFilter);

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

            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(monitor -> String.valueOf(monitor.getManufacturer()).contains(filterValue));
            }
            clearOtherFilters(manufacturerIdFilter);
            clearForm();
            refreshGrid();
        });
        manufacturerIdFilter.setPlaceholder("Filter");
        manufacturerIdFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("manufacturer.name")).setComponent(manufacturerIdFilter);

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

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

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

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

    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != modelFilter) {
            modelFilter.clear();
        }
        if (currentFilter != manufacturerIdFilter) {
            manufacturerIdFilter.clear();
        }
        if (currentFilter != screenSizeFilter) {
            screenSizeFilter.clear();
        }
        if (currentFilter != resolutionFilter) {
            resolutionFilter.clear();
        }
        if (currentFilter != refreshrRateFilter) {
            refreshrRateFilter.clear();
        }
        if (currentFilter != priceFilter) {
            priceFilter.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();
        model = new TextField("Model");
        model.setRequired(true);

        manufacturer = new ComboBox<Manufacturer>("Manufacturer");
        manufacturer.setItemLabelGenerator(currManufacturer -> String.valueOf(currManufacturer.getId()));
        manufacturer.setAllowCustomValue(false);
        manufacturer.setRequired(true);
        manufacturer.addFocusListener(event -> {
            List<Manufacturer> manufacturerList = manufacturerService.list();
            manufacturer.setItems(manufacturerList);
        });
        manufacturer.setItemLabelGenerator(manufacturer ->
                manufacturer != null ? manufacturer.getName() : "");

        screenSize = new TextField("Screen Size");
        screenSize.setRequired(true);
        resolution = new TextField("Resolution");
        resolution.setRequired(true);
        refreshrRate = new TextField("Refreshr Rate");
        refreshrRate.setRequired(true);
        price = new TextField("Price");
        price.setRequired(true);
        formLayout.add(model, manufacturer, screenSize, resolution, refreshrRate, price);

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

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


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

        dataProvider.setFilter(currentFilter);

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

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

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

        binder.readBean(this.monitor);


    }

    private TextField modelFilter;
    private TextField manufacturerIdFilter;
    private TextField screenSizeFilter;
    private TextField resolutionFilter;
    private TextField refreshrRateFilter;
    private TextField priceFilter;
}