package de.assecutor.pdftool.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.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
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.EmailField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.assecutor.pdftool.address.Address;
import de.assecutor.pdftool.address.AddressRepository;
import de.assecutor.pdftool.address.AddressType;
import de.assecutor.pdftool.invoice.VatIdValidator;
import org.springframework.data.domain.Sort;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

/**
 * Adressbuch: Übersicht aller Firmenadressen (Rechnungssteller und
 * Rechnungsempfänger) mit Anlegen, Bearbeiten und Löschen.
 */
@Route(value = "adressbuch", layout = MainLayout.class)
@PageTitle("Adressbuch")
public class AddressBookView extends VerticalLayout {

    private static final String EMAIL_PATTERN = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";

    private final AddressRepository repository;

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

    /** Filter über der Tabelle: alle Adressen oder nur ein Verwendungszweck. */
    private final ComboBox<AddressType> typeFilter = new ComboBox<>();

    public AddressBookView(AddressRepository repository) {
        this.repository = repository;

        setSizeFull();

        typeFilter.setItems(AddressType.values());
        typeFilter.setItemLabelGenerator(type -> type.getLabel());
        typeFilter.setPlaceholder("Alle Adressen");
        typeFilter.setClearButtonVisible(true);
        typeFilter.setWidth("260px");
        typeFilter.addValueChangeListener(event -> refreshGrid());

        Button addAddress = new Button("Neue Adresse", new Icon(VaadinIcon.PLUS));
        addAddress.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        addAddress.addClickListener(event -> openEditDialog(null));

        HorizontalLayout toolbar = new HorizontalLayout(typeFilter, addAddress);
        toolbar.setWidthFull();
        toolbar.setJustifyContentMode(JustifyContentMode.BETWEEN);
        toolbar.setAlignItems(Alignment.CENTER);
        add(toolbar);

        grid.addComponentColumn(address -> {
            boolean isSender = address.getType() == AddressType.RECHNUNGSSTELLER;
            Icon icon = (isSender ? VaadinIcon.PAPERPLANE_O : VaadinIcon.INBOX).create();
            icon.setSize("var(--lumo-icon-size-s)");
            icon.getStyle().set("color", "var(--lumo-secondary-text-color)");
            icon.getElement().setAttribute("title",
                    address.getType() != null ? address.getType().getLabel() : "");
            return icon;
        })
                .setHeader("Typ")
                .setSortable(true)
                .setComparator(Comparator.comparing(
                        address -> address.getType() != null ? address.getType().getLabel() : ""))
                .setFlexGrow(0)
                .setAutoWidth(true);
        grid.addColumn(address -> address.getName()).setHeader("Firma / Name").setSortable(true).setAutoWidth(true);
        grid.addColumn(address -> address.getStreet()).setHeader("Straße").setAutoWidth(true);
        grid.addComponentColumn(address -> {
            Button edit = new Button(new Icon(VaadinIcon.EDIT), event -> openEditDialog(address));
            edit.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL);
            edit.setTooltipText("Bearbeiten");
            Button delete = new Button(new Icon(VaadinIcon.TRASH), event -> confirmDelete(address));
            delete.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ERROR,
                    ButtonVariant.LUMO_SMALL);
            delete.setTooltipText("Löschen");
            return new HorizontalLayout(edit, delete);
        }).setHeader("").setFlexGrow(0).setAutoWidth(true);
        grid.addItemDoubleClickListener(event -> openEditDialog(event.getItem()));
        grid.setSizeFull();
        add(grid);
        expand(grid);

        refreshGrid();
    }

    private void refreshGrid() {
        AddressType filter = typeFilter.getValue();
        List<Address> addresses = filter == null
                ? repository.findAll(Sort.by(Sort.Order.asc("type"), Sort.Order.asc("name")))
                : repository.findByType(filter);
        addresses = addresses.stream()
                .sorted(Comparator
                        .comparing((Address address) -> address.getType() != null ? address.getType().ordinal() : 0)
                        .thenComparing(address -> address.getName() != null ? address.getName() : "",
                                String.CASE_INSENSITIVE_ORDER))
                .toList();
        grid.setItems(addresses);
    }

    /** Öffnet den Dialog zum Anlegen ({@code address == null}) bzw. Bearbeiten einer Adresse. */
    private void openEditDialog(Address address) {
        boolean isNew = address == null;
        Address target = address == null ? new Address() : address;

        Dialog dialog = new Dialog();
        dialog.setHeaderTitle(isNew ? "Neue Adresse" : "Adresse bearbeiten");

        ComboBox<AddressType> type = new ComboBox<>("Typ");
        type.setItems(AddressType.values());
        type.setItemLabelGenerator(value -> value.getLabel());
        type.setRequired(true);
        type.setValue(target.getType() != null ? target.getType()
                : (typeFilter.getValue() != null ? typeFilter.getValue() : AddressType.RECHNUNGSEMPFAENGER));

        TextField name = requiredField("Firma / Name", target.getName());
        TextField street = requiredField("Straße und Hausnummer", target.getStreet());
        TextField zip = requiredField("PLZ", target.getZip());
        TextField city = requiredField("Ort", target.getCity());
        TextField countryCode = requiredField("Land (ISO-Code, z.B. DE)",
                target.getCountryCode() != null ? target.getCountryCode() : "DE");
        countryCode.setMaxLength(2);
        countryCode.setPattern("[A-Za-z]{2}");
        TextField vatId = new TextField("USt-IdNr.");
        vatId.setValue(target.getVatId() != null ? target.getVatId() : "");
        vatId.setHelperText("z. B. DE261094748");
        EmailField email = new EmailField("E-Mail");
        email.setValue(target.getEmail() != null ? target.getEmail() : "");
        // E-Mail direkt beim Verlassen des Feldes prüfen
        email.addValueChangeListener(event -> {
            String value = event.getValue();
            boolean valid = value == null || value.isBlank() || value.matches(EMAIL_PATTERN);
            email.setErrorMessage("Gültige E-Mail-Adresse angeben.");
            email.setInvalid(!valid);
        });
        TextField phone = new TextField("Telefon");
        phone.setValue(target.getPhone() != null ? target.getPhone() : "");

        FormLayout form = new FormLayout(type, name, street, zip, city, countryCode, vatId, email, phone);
        form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("500px", 2));
        form.setWidth("560px");
        dialog.add(form);

        Button cancel = new Button("Abbrechen", event -> dialog.close());
        Button save = new Button("Speichern", event -> {
            List<String> errors = new ArrayList<>();
            if (type.getValue() == null) {
                type.setInvalid(true);
                errors.add("Typ ist ein Pflichtfeld.");
            }
            validateRequired(name, "Firma / Name", errors);
            validateRequired(street, "Straße und Hausnummer", errors);
            validateRequired(zip, "PLZ", errors);
            validateRequired(city, "Ort", errors);
            if (validateRequired(countryCode, "Land", errors)
                    && !countryCode.getValue().trim().matches("[A-Za-z]{2}")) {
                countryCode.setErrorMessage("Land muss ein zweistelliger ISO-Code sein.");
                countryCode.setInvalid(true);
                errors.add("Land muss ein zweistelliger ISO-Code sein.");
            }
            if (!vatId.getValue().isBlank()) {
                Optional<String> vatError = VatIdValidator.validate(vatId.getValue());
                if (vatError.isPresent()) {
                    vatId.setErrorMessage(vatError.get());
                    vatId.setInvalid(true);
                    errors.add(vatError.get());
                }
            }
            if (!email.getValue().isBlank() && !email.getValue().matches(EMAIL_PATTERN)) {
                email.setErrorMessage("Gültige E-Mail-Adresse angeben.");
                email.setInvalid(true);
                errors.add("E-Mail-Adresse ist ungültig.");
            }
            if (!errors.isEmpty()) {
                Notification.show(String.join("\n", errors), 5000, Notification.Position.MIDDLE)
                        .addThemeVariants(NotificationVariant.LUMO_ERROR);
                return;
            }

            target.setType(type.getValue());
            target.setName(name.getValue().trim());
            target.setStreet(street.getValue().trim());
            target.setZip(zip.getValue().trim());
            target.setCity(city.getValue().trim());
            target.setCountryCode(countryCode.getValue().trim().toUpperCase());
            target.setVatId(vatId.getValue().isBlank() ? "" : VatIdValidator.normalize(vatId.getValue()));
            target.setEmail(email.getValue().trim());
            target.setPhone(phone.getValue().trim());
            target.touch();
            repository.save(target);

            dialog.close();
            refreshGrid();
            Notification.show(isNew ? "Adresse angelegt." : "Adresse gespeichert.", 3000,
                            Notification.Position.BOTTOM_START)
                    .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
        });
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        dialog.getFooter().add(cancel, save);

        dialog.open();
        name.focus();
    }

    private void confirmDelete(Address address) {
        ConfirmDialog confirm = new ConfirmDialog();
        confirm.setHeader("Adresse löschen?");
        confirm.setText("Soll die Adresse \"" + address.getName() + "\" wirklich gelöscht werden?");
        confirm.setConfirmText("Löschen");
        confirm.setConfirmButtonTheme("error primary");
        confirm.setCancelable(true);
        confirm.setCancelText("Abbrechen");
        confirm.addConfirmListener(event -> {
            repository.delete(address);
            refreshGrid();
            Notification.show("Adresse gelöscht.", 3000, Notification.Position.BOTTOM_START);
        });
        confirm.open();
    }

    private static TextField requiredField(String label, String value) {
        TextField field = new TextField(label);
        field.setRequired(true);
        field.setValue(value != null ? value : "");
        return field;
    }

    /** @return true, wenn das Pflichtfeld gefüllt ist; markiert es sonst als ungültig. */
    private static boolean validateRequired(TextField field, String label, List<String> errors) {
        if (field.getValue() == null || field.getValue().isBlank()) {
            field.setErrorMessage(label + " ist ein Pflichtfeld.");
            field.setInvalid(true);
            errors.add(label + " ist ein Pflichtfeld.");
            return false;
        }
        field.setInvalid(false);
        return true;
    }
}
