package de.votian.web.view;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.votian.web.model.SessionData;
import de.votian.web.service.RestClientService;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Route(value = "public-holidays", layout = MainLayout.class)
@PageTitle("Votian - Feiertage")
public class PublicHolidayView extends VerticalLayout {

    private final RestClientService restClient;
    private final SessionData sessionData;

    private final ComboBox<Integer> yearSelect = new ComboBox<>("Jahr");
    private final ComboBox<Integer> mergeYearSelect = new ComboBox<>("Vorjahr integrieren");
    private final Button mergeButton = new Button("Eintraege uebernehmen");
    private final Button saveButton = new Button("Speichern");
    private final Paragraph info = new Paragraph();
    private final Grid<HolidayRow> grid = new Grid<>(HolidayRow.class, false);

    private final List<HolidayRow> rows = new ArrayList<>();
    private boolean loading;

    public PublicHolidayView(RestClientService restClient, SessionData sessionData) {
        this.restClient = restClient;
        this.sessionData = sessionData;

        setSizeFull();
        setPadding(true);
        setSpacing(true);

        configureToolbar();
        configureGrid();

        add(new H3("Feiertage"), new HorizontalLayout(yearSelect, mergeYearSelect, mergeButton, saveButton), info, grid);
        expand(grid);

        loadYear(LocalDate.now().getYear());
    }

    private void configureToolbar() {
        yearSelect.setWidth("160px");
        yearSelect.addValueChangeListener(event -> {
            if (!loading && event.getValue() != null) {
                loadYear(event.getValue());
            }
        });

        mergeYearSelect.setWidth("220px");
        mergeYearSelect.addValueChangeListener(event -> refreshButtons());

        mergeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        mergeButton.addClickListener(event -> mergeSelectedYear());

        saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        saveButton.addClickListener(event -> save());
    }

    private void configureGrid() {
        grid.addColumn(HolidayRow::isoDate).setHeader("Datum").setAutoWidth(true).setFrozen(true);
        grid.addColumn(HolidayRow::weekdayName).setHeader("Wochentag").setAutoWidth(true);
        grid.addComponentColumn(this::buildHolidayCheckbox).setHeader("Feiertag").setAutoWidth(true);
        grid.addComponentColumn(this::buildNameField).setHeader("Bezeichnung").setFlexGrow(1);
        grid.setSizeFull();
    }

    private Checkbox buildHolidayCheckbox(HolidayRow row) {
        Checkbox checkbox = new Checkbox(row.holiday());
        checkbox.setEnabled(row.editable());
        checkbox.addValueChangeListener(event -> {
            row.setHoliday(event.getValue());
            if (!event.getValue()) {
                row.setName("");
            }
            grid.getDataProvider().refreshItem(row);
        });
        return checkbox;
    }

    private TextField buildNameField(HolidayRow row) {
        TextField field = new TextField();
        field.setWidthFull();
        field.setValue(row.name());
        field.setEnabled(row.editable() && row.holiday());
        field.setPlaceholder("Feiertagsname");
        field.addValueChangeListener(event -> row.setName(event.getValue()));
        return field;
    }

    private void loadYear(int year) {
        if (sessionData.getHeadquartersId() == null) {
            return;
        }
        try {
            loading = true;
            HolidayResponse response = parseResponse(restClient.getMap(
                    "/public-holidays?hqId=" + sessionData.getHeadquartersId() + "&year=" + year
            ));
            yearSelect.setItems(response.availableYears());
            yearSelect.setValue(response.year());
            mergeYearSelect.setItems(response.copySourceYears());
            if (!response.copySourceYears().contains(mergeYearSelect.getValue())) {
                mergeYearSelect.clear();
            }
            rows.clear();
            rows.addAll(response.days());
            grid.setItems(rows);
            info.setText(response.editable()
                    ? "Nur zukuenftige Tage sind bearbeitbar. Vergangene Feiertage bleiben beim Speichern unveraendert."
                    : "Dieses Jahr ist nur noch lesend verfuegbar.");
            refreshButtons();
        } catch (Exception e) {
            Notification.show("Feiertage konnten nicht geladen werden: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        } finally {
            loading = false;
        }
    }

    private void mergeSelectedYear() {
        Integer sourceYear = mergeYearSelect.getValue();
        Integer targetYear = yearSelect.getValue();
        if (sourceYear == null || targetYear == null) {
            return;
        }
        try {
            HolidayResponse source = parseResponse(restClient.getMap(
                    "/public-holidays?hqId=" + sessionData.getHeadquartersId() + "&year=" + sourceYear
            ));
            Map<String, HolidayRow> sourceByDate = new LinkedHashMap<>();
            for (HolidayRow row : source.days()) {
                if (row.holiday()) {
                    sourceByDate.put(row.isoDate().substring(5), row);
                }
            }

            int merged = 0;
            for (HolidayRow targetRow : rows) {
                if (!targetRow.editable() || targetRow.holiday()) {
                    continue;
                }
                HolidayRow sourceRow = sourceByDate.get(targetRow.isoDate().substring(5));
                if (sourceRow == null) {
                    continue;
                }
                targetRow.setHoliday(true);
                targetRow.setName(sourceRow.name());
                merged++;
            }
            grid.getDataProvider().refreshAll();
            Notification.show(merged + " Feiertage integriert.", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
        } catch (Exception e) {
            Notification.show("Vorjahreseintraege konnten nicht geladen werden: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void save() {
        Integer year = yearSelect.getValue();
        if (year == null || sessionData.getHeadquartersId() == null) {
            return;
        }
        try {
            List<Map<String, Object>> days = rows.stream().map(HolidayRow::toMap).toList();
            restClient.put(
                    "/public-holidays?hqId=" + sessionData.getHeadquartersId() + "&year=" + year,
                    Map.of("days", days)
            );
            Notification.show("Feiertage gespeichert.", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
            loadYear(year);
        } catch (Exception e) {
            Notification.show("Feiertage konnten nicht gespeichert werden: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void refreshButtons() {
        boolean editable = rows.stream().anyMatch(HolidayRow::editable);
        mergeButton.setEnabled(editable && mergeYearSelect.getValue() != null);
        saveButton.setEnabled(editable && sessionData.canManageHeadquarters());
    }

    private HolidayResponse parseResponse(Map<String, Object> data) {
        List<Integer> availableYears = new ArrayList<>();
        Object rawYears = data.get("availableYears");
        if (rawYears instanceof List<?> list) {
            for (Object entry : list) {
                Integer year = toInteger(entry);
                if (year != null) {
                    availableYears.add(year);
                }
            }
        }

        List<Integer> copySourceYears = new ArrayList<>();
        Object rawCopyYears = data.get("copySourceYears");
        if (rawCopyYears instanceof List<?> list) {
            for (Object entry : list) {
                Integer year = toInteger(entry);
                if (year != null) {
                    copySourceYears.add(year);
                }
            }
        }

        List<HolidayRow> parsedRows = new ArrayList<>();
        Object rawDays = data.get("days");
        if (rawDays instanceof List<?> list) {
            for (Object entry : list) {
                if (!(entry instanceof Map<?, ?> rawMap)) {
                    continue;
                }
                parsedRows.add(new HolidayRow(
                        str(rawMap.get("isoDate")),
                        str(rawMap.get("weekdayName")),
                        toInteger(rawMap.get("month")),
                        toInteger(rawMap.get("day")),
                        bool(rawMap.get("holiday")),
                        str(rawMap.get("name")),
                        bool(rawMap.get("editable"))
                ));
            }
        }

        return new HolidayResponse(
                toInteger(data.get("year")) != null ? toInteger(data.get("year")) : LocalDate.now().getYear(),
                availableYears,
                copySourceYears,
                parsedRows,
                bool(data.get("editable"))
        );
    }

    private Integer toInteger(Object value) {
        if (value instanceof Number number) {
            return number.intValue();
        }
        try {
            return value != null ? Integer.parseInt(value.toString()) : null;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private boolean bool(Object value) {
        return Boolean.TRUE.equals(value) || (value != null && "true".equalsIgnoreCase(value.toString()));
    }

    private String str(Object value) {
        return value != null ? value.toString() : "";
    }

    private record HolidayResponse(int year,
                                   List<Integer> availableYears,
                                   List<Integer> copySourceYears,
                                   List<HolidayRow> days,
                                   boolean editable) {
    }

    private static final class HolidayRow {
        private final String isoDate;
        private final String weekdayName;
        private final Integer month;
        private final Integer day;
        private boolean holiday;
        private String name;
        private final boolean editable;

        private HolidayRow(String isoDate,
                           String weekdayName,
                           Integer month,
                           Integer day,
                           boolean holiday,
                           String name,
                           boolean editable) {
            this.isoDate = isoDate;
            this.weekdayName = weekdayName;
            this.month = month;
            this.day = day;
            this.holiday = holiday;
            this.name = name != null ? name : "";
            this.editable = editable;
        }

        private String isoDate() {
            return isoDate;
        }

        private String weekdayName() {
            return weekdayName;
        }

        private boolean holiday() {
            return holiday;
        }

        private void setHoliday(boolean holiday) {
            this.holiday = holiday;
        }

        private String name() {
            return name;
        }

        private void setName(String name) {
            this.name = name != null ? name : "";
        }

        private boolean editable() {
            return editable;
        }

        private Map<String, Object> toMap() {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("month", month);
            map.put("day", day);
            map.put("holiday", holiday);
            map.put("name", name);
            return map;
        }
    }
}
