package be.iminds.ilabt.jfed.ui.javafx.probe_gui.credential_manipulation;

import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.model.ParameterHistoryModel;
import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.CredentialException;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.SfaCredential;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.ui.javafx.probe_gui.command_arguments.CommandArgumentChooser;
import be.iminds.ilabt.jfed.ui.javafx.probe_gui.command_arguments.CredentialArgumentChooser;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.JavaFXLogger;
import be.iminds.ilabt.jfed.util.KeyUtil;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;

import javax.inject.Inject;
import java.io.File;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;

/**
 * CredentialCreatorPanel
 */
public class DelegatedCredentialCreatorPanelController extends CredentialCreatorPanel {
    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(DelegatedCredentialCreatorPanelController.class);

    @FXML
    private BorderPane root;

    @FXML
    private Label credentialLabel;
    @FXML
    private VBox credentialChooserBox;
    @FXML
    private CredentialArgumentChooser credentialChooser;

    @FXML
    private Label targetLabel;
    @FXML
    private TextField targetText;

    @FXML
    private Button generateButton;

    @FXML
    private Label resultLabel;
    @FXML
    private TextArea resultText;
    @FXML
    private Button saveResultButton;

    @FXML
    private Label privNameLabel;
    @FXML
    private TextField privNameText;
    @FXML
    private CheckBox canDelegateCheckBox;
    @FXML
    private Label expireLabel;
    @FXML
    private TextField expireText;

    @FXML
    private RadioButton targetViaUrn;
    @FXML
    private RadioButton targetViaCert;

    @FXML
    private VBox targetUrnBox;
    @FXML
    private VBox targetCertfileBox;

    @FXML
    private TextField targetCertFilename;
    @FXML
    private Button loadCertButton;

    private final ParameterHistoryModel parameterHistoryModel;

    @Inject
    DelegatedCredentialCreatorPanelController(JavaFXLogger logger, AuthorityFinder authorityFinder,
                                                     GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider,
                                                     SfaModel sfaModel, HighLevelTaskFactory hltf,
                                                     TaskThread tt, JFedPreferences jFedPreferences,
                                                     ParameterHistoryModel parameterHistoryModel) {
        super(logger, authorityFinder, geniUserProvider, connectionProvider, sfaModel, hltf, tt, jFedPreferences);
        this.parameterHistoryModel = parameterHistoryModel;
    }


    @FXML
    private void initialize() {
        credentialLabel.managedProperty().bind(credentialLabel.visibleProperty());
        credentialChooserBox.managedProperty().bind(credentialChooserBox.visibleProperty());
        targetLabel.managedProperty().bind(targetLabel.visibleProperty());
        targetText.managedProperty().bind(targetText.visibleProperty());
        generateButton.managedProperty().bind(generateButton.visibleProperty());
        resultLabel.managedProperty().bind(resultLabel.visibleProperty());
        resultText.managedProperty().bind(resultText.visibleProperty());
        saveResultButton.managedProperty().bind(saveResultButton.visibleProperty());
        targetUrnBox.managedProperty().bind(targetUrnBox.visibleProperty());
        targetCertfileBox.managedProperty().bind(targetCertfileBox.visibleProperty());

        privNameLabel.managedProperty().bind(privNameLabel.visibleProperty());
        privNameText.managedProperty().bind(privNameText.visibleProperty());
        canDelegateCheckBox.managedProperty().bind(canDelegateCheckBox.visibleProperty());
        expireLabel.managedProperty().bind(expireLabel.visibleProperty());
        expireText.managedProperty().bind(expireText.visibleProperty());

        credentialLabel.visibleProperty().bind(credentialChooserBox.visibleProperty());
        targetLabel.managedProperty().bind(targetText.visibleProperty());
        resultLabel.managedProperty().bind(resultText.visibleProperty());
        saveResultButton.managedProperty().bind(resultText.visibleProperty());

        privNameLabel.visibleProperty().bind(privNameText.visibleProperty());
        expireLabel.visibleProperty().bind(expireText.visibleProperty());

        targetUrnBox.visibleProperty().bind(targetViaUrn.selectedProperty());
        targetCertfileBox.visibleProperty().bind(targetViaCert.selectedProperty());

        //TODO: listen for user login/logout sfaModel.getGeniUserProvider().addListener

        credentialChooser =
                new CredentialArgumentChooser(parameterHistoryModel, CommandArgumentChooser.CredentialSubject.ANY);
        credentialChooserBox.getChildren().add(credentialChooser);
    }


    private X509Certificate userCert;

    @FXML
    private void loadCertFile() {
        FileChooser fileChooser = new FileChooser();
        File file = fileChooser.showOpenDialog(null);

        if (file != null) {
            String filename = file.getPath();
            targetCertFilename.setText(filename);
            String fileContent = null;
            try {
                fileContent = IOUtils.fileToString(file);
                userCert = KeyUtil.pemToX509Certificate(fileContent);
            } catch (Exception e) {
                LOG.error("Error reading user certificate file \"" + filename + "\": " + e.getMessage(), e);
                targetCertFilename.setText("ERROR: " + e.getMessage());
                userCert = null;
            }
        }
    }


    protected void setResult(final String text) {
        Platform.runLater(() -> resultText.setText(text));

    }


    Thread generateDelegatedThread = null;

    public void generate() {
        if (targetViaCert.isSelected() && userCert == null) {
            LOG.info("button clicked: generateDelegate not called since no certificate loaded yet");
            return;
        }

        if (generateDelegatedThread != null) {
            LOG.debug("button clicked: generateDelegate still running, so not called");
            return;
        } else
            LOG.debug("button clicked: generateDelegate called");

        generateButton.setDisable(true);


        AnyCredential delegatedCredential_any = credentialChooser.valueProperty().getValue();
        if (delegatedCredential_any == null || !(delegatedCredential_any instanceof SfaCredential)) {
            LOG.debug("generateDelegate_Helper() no usable credential selected: " + delegatedCredential_any == null
                    ? "null" :
                    ("class=" + delegatedCredential_any.getClass().getName() +
                            " type=" + delegatedCredential_any.getType() +
                            " version=" + delegatedCredential_any.getVersion())
            );
            return;
        }
        final SfaCredential delegatedCredential = (SfaCredential) delegatedCredential_any;

        int expireInDaysHelper;
        try {
            expireInDaysHelper = Integer.parseInt(expireText.getText());
        } catch (NumberFormatException e) {
            LOG.debug("Error processing \"expire\": \"" + expireText.getText() + "\" is not a valid integer", e);
            setResult("Error processing \"expire\": \"" + expireText.getText() + "\" is not a valid integer");
            return;
        }
        final int expireInDays = expireInDaysHelper;

        final String privilegeName = privNameText.getText();
        final boolean canDelegate = canDelegateCheckBox.isSelected();

        generateDelegatedThread = new Thread(() -> {
            String targetUrn;
            if (targetViaUrn.isSelected()) {
                targetUrn = targetText.getText();
                userCert = certificateRetriever(targetUrn);
            } else {
                assert userCert != null;
                targetUrn = findUrnInCertificate(userCert);
                assert targetUrn != null;
            }
            generate_Helper(delegatedCredential, targetUrn, userCert, expireInDays, privilegeName, canDelegate);
            LOG.trace("generateDelegate_Helper() returned");
            generateDelegatedThread = null;
            Platform.runLater(() -> generateButton.setDisable(false));
        });
        generateDelegatedThread.start();
    }

    public void generate_Helper(SfaCredential delegatedCredential, String targetUrn, X509Certificate targetCert, int expireInDays, String privilegeName, boolean canDelegate) {
        assert !Platform.isFxApplicationThread();
        assert credentialChooser != null;

        assert geniUserProvider.isUserLoggedIn();

        LOG.trace("generateDelegate_Helper() has target info");

        PrivateKey userPrivateKey = geniUserProvider.getLoggedInGeniUser().getPrivateKey();

        Date expireDate =
                new Date(System.currentTimeMillis() + (expireInDays * 24 * 60 * 60 * 1000)); //expireInDays days from now

        try {
            LOG.trace("generateDelegate_Helper() creating credential");
            final SfaCredential delegateCredential = delegatedCredential.delegate(
                    targetUrn, targetCert,
                    userPrivateKey,
                    expireDate, privilegeName, canDelegate);

            LOG.trace("generateDelegate_Helper() showing result");
            setResult(delegateCredential.getCredentialXml());

            Platform.runLater(() -> parameterHistoryModel.addUserCredential(delegateCredential));
        } catch (CredentialException e) {
            LOG.error("Exception creating speaks for credential", e);
            setResult("ERROR creating speaks for credential: " + e.getMessage());
        }
    }

    public void save() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Save Credential File");
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Credential XML Files", "*.xml"));
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All Files", "*.*"));
        File file = fileChooser.showSaveDialog(root.getScene().getWindow());
        if (file != null) {
            try {
                IOUtils.stringToFile(file, resultText.getText());
            } catch (Exception e) {
                LOG.error("Failed to save to file: " + file.getPath(), e);
            }
        }
    }
}
