/*
 * Copyright (C) 2025-2026  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.ui;

import org.aglais.zms.AglaisZMS;
import org.aglais.zms.ClientConfig;
import org.aglais.zms.util.ConfigUtil;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.ResourceBundle;

/**
 * The LoginDialog class represents a modal dialog for user authentication to connect to a database.
 * It allows users to input connection details such as host, database name, username, and password,
 * and submit these credentials to be validated via a listener.
 * <br>
 * The dialog provides options for confirming or canceling the login process and supports automatic
 * input population from command line arguments. If all required parameters are provided via command line,
 * the dialog will attempt login automatically without showing the UI.
 */
public class LoginDialog extends JDialog {

    private static final String LOG_TAG = LoginDialog.class.getSimpleName();

    private JPanel contentPane;
    private JButton okButton;
    private JButton cancelButton;
    private JTextField urlTextField;
    private JTextField usernameTextField;
    private JPasswordField passwordField;
    private JCheckBox rememberPasswordCheckBox;

    private final AglaisZMS zms;

    /**
     * Interface for handling login attempts and validating credentials
     */
    public interface ILoginListener {
        /**
         * Called when login is attempted to validate credentials
         *
         * @param dialog   The {@link LoginDialog} instance
         * @param url      API URL
         * @param username API user
         * @param password API password
         * @return true if login successful, false otherwise
         */
        boolean onLogin(LoginDialog dialog, String url, String username, char[] password);
    }

    private final ILoginListener loginListener;

    /**
     * Creates a new LoginDialog with the specified login listener
     *
     * @param zms           The {@link AglaisZMS} application instance
     * @param loginListener The listener that will handle login validation
     */
    public LoginDialog(AglaisZMS zms, @NotNull ILoginListener loginListener) {
        this.zms = zms;
        this.loginListener = loginListener;

        // Only show remember password when it is supported
        rememberPasswordCheckBox.setVisible(zms.encryptPassword(new char[] { '0' }) != null);
        rememberPasswordCheckBox.setSelected(zms.getConfig().server.encryptedPassword() != null);

        setContentPane(contentPane);
        setModal(true);
        getRootPane().setDefaultButton(okButton);

        okButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onOK();
            }
        });

        cancelButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });

        // call onCancel() when cross is clicked
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onCancel();
            }
        });

        // call onCancel() on ESCAPE
        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    /**
     * Handles the OK button click by attempting to log in with entered credentials
     *
     * @return True if login was successful
     */
    private boolean onOK() {
        // Update client configuration if needed
        ClientConfig config = zms.getConfig();
        ClientConfig.Server newServer = new ClientConfig.Server(urlTextField.getText(), usernameTextField.getText(),
                rememberPasswordCheckBox.isSelected() ? zms.encryptPassword(passwordField.getPassword()) : null);
        if (!newServer.equals(config.server) || !Arrays.equals(zms.decryptPassword(config.server.encryptedPassword()),
                rememberPasswordCheckBox.isSelected() ? passwordField.getPassword() : null)) {
            config.server = newServer;
            ConfigUtil.saveConfig(config.configPath, config);
        }

        if (loginListener.onLogin(this, urlTextField.getText(), usernameTextField.getText(),
                passwordField.getPassword())) {
            dispose();

            return true;
        } else {
            ResourceBundle uiBundle = ResourceBundle.getBundle("ui");

            JOptionPane.showMessageDialog(this, uiBundle.getString("error_login_failed"),
                    uiBundle.getString("error"), JOptionPane.ERROR_MESSAGE);
        }

        return false;
    }

    /**
     * Handles the Cancel button click by disposing the dialog
     */
    private void onCancel() {
        dispose();
    }

    /**
     * Initializes and displays the login dialog
     */
    public void start() {
        // Set parameters from config
        ClientConfig config = zms.getConfig();
        JTextField[] parameterFields = new JTextField[]{
                urlTextField,
                usernameTextField,
                passwordField
        };
        if (config.server != null) {
            urlTextField.setText(config.server.url());
            usernameTextField.setText(config.server.username());

            char[] password = zms.decryptPassword(config.server.encryptedPassword());
            if (password != null)
                passwordField.setText(new String(password));
        }

        // Check if all parameters are set
        JTextField firstMissing = null;
        for (JTextField parameterField : parameterFields) {
            if (parameterField.getText() == null || parameterField.getText().isEmpty()) {
                firstMissing = parameterField;

                break;
            }
        }

        if (firstMissing == null)
            if (onOK())
                return;

        pack();

        if (firstMissing != null)
            firstMissing.requestFocusInWindow();
        else
            parameterFields[parameterFields.length - 1].requestFocusInWindow();

        // Center the dialog
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int x = (screenSize.width - getWidth()) / 2;
        int y = (screenSize.height - getHeight()) / 2;
        setLocation(x, y);

        setVisible(true);
    }
}
