package p.studio.workspaces.assets.dialogs;

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import p.packer.dtos.PackerDiagnosticDTO;
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
import p.studio.Container;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;

import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public final class AssetDiagnosticsDialog {
    private static final String GENERAL_GROUP_KEY = "__general__";

    private final Stage stage;
    private final ProjectReference projectReference;
    private final String assetName;
    private final List<PackerDiagnosticDTO> diagnostics;
    private final Map<String, DiagnosticGroup> diagnosticsByGroup;

    private final VBox groupList = new VBox(6);
    private final VBox diagnosticsContent = new VBox(10);
    private String selectedGroupKey;

    private AssetDiagnosticsDialog(
            Window owner,
            ProjectReference projectReference,
            String assetName,
            List<PackerDiagnosticDTO> diagnostics) {
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.assetName = Objects.requireNonNull(assetName, "assetName");
        this.diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
        this.diagnosticsByGroup = buildGroups(this.diagnostics);
        this.selectedGroupKey = diagnosticsByGroup.containsKey(GENERAL_GROUP_KEY)
                ? GENERAL_GROUP_KEY
                : diagnosticsByGroup.keySet().stream().findFirst().orElse(GENERAL_GROUP_KEY);
        this.stage = new Stage();
        stage.initOwner(owner);
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setTitle(Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_DIALOG_TITLE));
        stage.setScene(new Scene(buildRoot(), 720, 560));
        stage.getScene().getStylesheets().add(Container.theme().getDefaultTheme());
    }

    public static void showAndWait(
            Window owner,
            ProjectReference projectReference,
            String assetName,
            List<PackerDiagnosticDTO> diagnostics) {
        new AssetDiagnosticsDialog(owner, projectReference, assetName, diagnostics).stage.showAndWait();
    }

    private VBox buildRoot() {
        final Label title = new Label(Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_DIALOG_TITLE));
        title.getStyleClass().add("studio-launcher-section-title");

        final Label subtitle = new Label(Container.i18n().format(
                I18n.ASSETS_DIAGNOSTICS_DIALOG_SUMMARY,
                diagnostics.size(),
                assetName));
        subtitle.getStyleClass().add("studio-launcher-subtitle");
        subtitle.setWrapText(true);

        diagnosticsContent.getStyleClass().add("assets-diagnostics-dialog-content");

        final ScrollPane scrollPane = new ScrollPane(diagnosticsContent);
        scrollPane.setFitToWidth(true);
        scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrollPane.getStyleClass().add("assets-diagnostics-dialog-scroll");

        groupList.getStyleClass().add("assets-diagnostics-dialog-groups");
        final ScrollPane groupScroll = new ScrollPane(groupList);
        groupScroll.setFitToWidth(true);
        groupScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        groupScroll.getStyleClass().add("assets-diagnostics-dialog-groups-scroll");

        final HBox browser = new HBox(16, groupScroll, scrollPane);
        browser.getStyleClass().add("assets-diagnostics-dialog-browser");
        HBox.setHgrow(scrollPane, Priority.ALWAYS);
        renderGroups();
        renderSelectedGroup();

        final Button closeButton = new Button();
        closeButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_CANCEL));
        closeButton.getStyleClass().addAll("studio-button", "studio-button-cancel");
        closeButton.setOnAction(ignored -> stage.close());

        final HBox actions = new HBox(closeButton);
        actions.setAlignment(Pos.CENTER_RIGHT);

        final VBox root = new VBox(16, title, subtitle, browser, actions);
        root.setPadding(new Insets(24));
        VBox.setVgrow(browser, Priority.ALWAYS);
        return root;
    }

    private void renderGroups() {
        groupList.getChildren().clear();
        if (diagnosticsByGroup.isEmpty()) {
            return;
        }
        for (DiagnosticGroup group : diagnosticsByGroup.values()) {
            final Button button = new Button(group.title() + " (" + group.diagnostics().size() + ")");
            button.getStyleClass().addAll("assets-diagnostics-dialog-group-button");
            if (Objects.equals(selectedGroupKey, group.key())) {
                button.getStyleClass().add("assets-diagnostics-dialog-group-button-selected");
            }
            button.setMaxWidth(Double.MAX_VALUE);
            button.setAlignment(Pos.CENTER_LEFT);
            button.setOnAction(ignored -> {
                selectedGroupKey = group.key();
                renderGroups();
                renderSelectedGroup();
            });
            groupList.getChildren().add(button);
        }
    }

    private void renderSelectedGroup() {
        diagnosticsContent.getChildren().clear();
        final DiagnosticGroup group = diagnosticsByGroup.get(selectedGroupKey);
        if (group == null || group.diagnostics().isEmpty()) {
            diagnosticsContent.getChildren().add(AssetDetailsUiSupport.createSectionMessage(
                    Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_EMPTY)));
            return;
        }

        final Label groupTitle = new Label(group.title());
        groupTitle.getStyleClass().add("assets-diagnostics-dialog-group-title");
        diagnosticsContent.getChildren().add(groupTitle);
        group.diagnostics().forEach(diagnostic -> diagnosticsContent.getChildren().add(createDiagnosticCard(diagnostic)));
    }

    private VBox createDiagnosticCard(PackerDiagnosticDTO diagnostic) {
        final Label severity = new Label(severityLabel(diagnostic));
        severity.getStyleClass().add("assets-details-diagnostic-severity");

        final Label message = new Label(diagnostic.message());
        message.getStyleClass().add("assets-details-diagnostic-message");
        message.setWrapText(true);

        final VBox card = new VBox(8, severity, message);
        card.getStyleClass().addAll("assets-details-diagnostic-card", severityToneClass(diagnostic.severity()));

        card.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
                Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_LABEL_CATEGORY),
                diagnostic.category().name()));
        card.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
                Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_LABEL_BLOCKING),
                AssetDetailsUiSupport.booleanLabel(diagnostic.blocking())));
        card.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
                Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_LABEL_EVIDENCE),
                evidenceLabel(diagnostic.evidencePath())));
        return card;
    }

    private String severityLabel(PackerDiagnosticDTO diagnostic) {
        return diagnostic.blocking()
                ? diagnostic.severity().name() + " / BLOCKING"
                : diagnostic.severity().name();
    }

    private String severityToneClass(PackerDiagnosticSeverity severity) {
        return switch (severity) {
            case ERROR -> "assets-details-diagnostic-blocker";
            case WARNING -> "assets-details-diagnostic-warning";
            case INFO -> "assets-details-diagnostic-hint";
        };
    }

    private String evidenceLabel(Path evidencePath) {
        if (evidencePath == null) {
            return "—";
        }
        return AssetDetailsUiSupport.projectRelativePath(projectReference, evidencePath);
    }

    private Map<String, DiagnosticGroup> buildGroups(List<PackerDiagnosticDTO> diagnostics) {
        final Map<String, DiagnosticGroup> grouped = new LinkedHashMap<>();
        for (PackerDiagnosticDTO diagnostic : diagnostics) {
            final String key = groupKey(diagnostic.evidencePath());
            final String title = groupTitle(key, diagnostic.evidencePath());
            grouped.computeIfAbsent(key, ignored -> new DiagnosticGroup(key, title, new java.util.ArrayList<>()))
                    .diagnostics()
                    .add(diagnostic);
        }
        final Map<String, DiagnosticGroup> ordered = new LinkedHashMap<>();
        ordered.put(GENERAL_GROUP_KEY, grouped.getOrDefault(
                GENERAL_GROUP_KEY,
                new DiagnosticGroup(GENERAL_GROUP_KEY, "General", new java.util.ArrayList<>())));
        for (Map.Entry<String, DiagnosticGroup> entry : grouped.entrySet()) {
            if (!GENERAL_GROUP_KEY.equals(entry.getKey())) {
                ordered.put(entry.getKey(), entry.getValue());
            }
        }
        return ordered.entrySet().stream()
                .filter(entry -> !entry.getValue().diagnostics().isEmpty() || GENERAL_GROUP_KEY.equals(entry.getKey()))
                .collect(java.util.stream.Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (left, right) -> left,
                        LinkedHashMap::new));
    }

    private String groupKey(Path evidencePath) {
        if (evidencePath == null) {
            return GENERAL_GROUP_KEY;
        }
        final String relativePath = AssetDetailsUiSupport.projectRelativePath(projectReference, evidencePath);
        if (relativePath.endsWith("/asset.json") || relativePath.equals("asset.json")) {
            return GENERAL_GROUP_KEY;
        }
        return relativePath;
    }

    private String groupTitle(String key, Path evidencePath) {
        if (GENERAL_GROUP_KEY.equals(key)) {
            return "General";
        }
        if (evidencePath == null) {
            return "General";
        }
        return evidencePath.getFileName() == null ? key : evidencePath.getFileName().toString();
    }

    private record DiagnosticGroup(
            String key,
            String title,
            List<PackerDiagnosticDTO> diagnostics) {
    }
}
